// An illustration of an extreme static type system // x/0 is a type error // // The present code implements a type system which assigns a distinct // type to every number. The compiler can infer these specific types for // results of arithmetic expressions. This can help the compiler catch // _all_ the errors. In particular, an expression whose evaluation // includes a division by zero is flagged by the _compiler_ as mistyped. // This static type system is as specific as it can ever be. // // The present code also shows C++ compiler as a term-rewriting engine -- // an interpreter of untyped lambda-calculus over type identifiers. // // The code defines rules of the type arithmetics (addition, subtraction, // and division) of integers represented as types. It then computes // and prints out the result of an expression (3*x+1)/(x-1) for x=4. // To be precise, the program prints out the value corresponding to the // type of that expression. // See a web site // http://pobox.com/~oleg/ftp/Computation/type-arithmetics.html // for more details. // // This code is self-contained. You can run it as // g++ lambda-interp.cc -o lambda-interp // ./lambda-interp // // Or, to specify your own value of x, for example, 3, // g++ -DARG1=3 lambda-interp.cc -o lambda-interp // ./lambda-interp // To include a statement that attempts to _eventually_ perform division // by zero, compile the code as: // g++ -DARG1=1 lambda-interp.cc -o lambda-interp // // The compiler finds a type error in that statement. Therefore, the // compilation fails, and no object file (let alone executable) is // produced. // // This code was compiled with egcs-2.91.66 and g++ 2.95.2 // on FreeBSD 3.2-4.0 and Linux 2.2.x // Any C++ compiler that is able to perform a partial template instantiation // will do. // // $Id: lambda-interp.cc,v 2.5 2000/08/26 00:23:47 oleg Exp oleg $ #include // Three base types of our static type system class Zero { public: enum { value = 0 }; int eval(void) const { return value; } }; template class Succ { public: enum { value = T::value + 1 }; int eval(void) const { return value; } }; template class Neg { public: enum { value = - T::value }; int eval(void) const { return value; } }; // The following classes define type inference rules for an addition operation // Note that these classes have no members -- they merely specify // inference rules for our (type) term re-writing template struct Add { // Traits class }; // (a+1) + (b+1) ==> a + (b + 1 + 1) template struct Add, Succ > { typedef typename Add > >::sum sum; }; template struct Add { // 0 + a ==> a typedef T sum; }; template struct Add,T> { typedef T sum; }; template struct Add { typedef T sum; }; template struct Add > { typedef T sum; }; struct Add { // 0 + 0 ==> 0 typedef Zero sum; }; // (a+1) + (- (b + 1 + 1)) ==> a + (- (b + 1)) template struct Add, Neg > > > { typedef typename Add > >::sum sum; }; // (a+1) + (- (0 + 1)) ==> a + 0 template struct Add, Neg > > { typedef typename Add::sum sum; }; // (- a) + (- b) ==> - (a + b) template struct Add, Neg > { typedef typename Add::sum sum_abs; typedef Neg sum; }; // (- a) + b ==> b + (- a) template struct Add, T2 > { typedef typename Add >::sum sum; }; // Negation, addition, and subtraction operations themselves // (appropriately typed!) template Neg > neg(Succ) { return Neg > (); } Zero neg(Zero t) { return t; } template typename Add::sum add(T1, T2) { typename Add::sum c; return c; } template typename Add >::sum sub(T1, T2) { typename Add >::sum c; return c; } // The following classes define type inference rules for a division operation template struct Div { // Traits class }; // a / (- b) ==> - (a/b) template struct Div > { typedef typename Div::ratio ratio_abs; typedef Neg ratio; }; // (- a) / b ==> - (a/b) template struct Div, T2 > { typedef typename Div::ratio ratio_abs; typedef Neg ratio; }; // (- a) / (- b) ==> a/b template struct Div, Neg > { typedef typename Div::ratio ratio; }; // a / 1 ==> a template struct Div > { typedef T ratio; }; // "Perform" division by repeated subtractions of the Divisor from // the Dividend, until the result becomes Zero or Negative. // Ratio counts the number of the subtractions performed. template struct DivEval { // Traits class }; template struct DivEval { typedef Ratio value; }; template struct DivEval, Divisor, Ratio> { typedef typename Add > >::sum value; }; template struct DivEval, Divisor, Ratio> { typedef typename Add< Succ, Neg >::sum new_dividend; typedef typename DivEval< new_dividend, Divisor, Succ >::value value; }; template struct Div > { typedef typename DivEval, Zero>::value ratio; }; // Division operation itself (appropriately typed!) template typename Div::ratio div(T1, T2) { typename Div::ratio c; return c; } // An example of complex, "user-defined functions" // (or, user-defined type constructors to be precise). // In this example, we will be computing (3*x+1)/(x-1), with the help of a // "temporary type variables" #if 0 // The following is a type constructor that computes (3*x+1) // It corresponds to the following sequence in the value arithemtics: // val y = x + x; // val z = y + x; // return z + 1; template struct Foo { typedef typename Add::sum y; typedef typename Add::sum z; typedef typename Add >::sum result; }; #endif #if 1 // The following is a type constructor that computes (3*x+1) // It is set to emulate an expression: // let y = x + x in let z = y + x in z + 1 template struct Foo { template struct body { template struct body2 { typedef typename Add >::sum result; }; typedef typename body2::sum>::result result; }; typedef typename body::sum>::result result; }; #endif #if 0 // The following evaluates "let var = exp in body;" // It was a great idea, but it doesn't work out: // the C preprocessor can't handle embedded commas #define XY(X,Y) X##Y #define MakeNameXY(FX,LINE) XY(FX,LINE) #define MakeName(FX) MakeNameXY(FX,__LINE__) #define LET(var,exp,body) \ template struct MakeName(__BODY__##var) { \ typedef typename body result; }; \ typedef typename MakeName(__BODY__##var) :: result result; // The following is a function that computes (3*x+1) // It is set to emulate an expression: // let y = x + x in let y = y + x in y + 1; // even more closely template struct Foo { LET(y,Add::sum, LET(y,Add::sum,Add >::sum)); }; #endif // of the great unworkable idea // The following type constructor computes Foo(x)/(x-1) template struct Bar { typedef typename Foo::result y; typedef typename Add > >::sum d; typedef typename Div::ratio result; }; // The following is a converter from a "value" to a "type" // Use it in the context // type_of(val).type or "typename type_of(val).type" template struct TypeOf { typedef T result; result type; TypeOf(T x) {} }; template TypeOf type_of(T x) { return x; } // The following "functions" that "convert a value to a type" // aren't strictly necessary. // if classes Foo and Bar contained the following // template struct Foo { ...; Foo(x _x) {} result type; }; // The one could write // SHOWVAL( Foo(two).type ) template typename Foo::result foo(TX) { Foo::result type; return type; } template typename Bar::result bar(TX) { Bar::result type; return type; } // Convert a natural number to a "unary" notation as a sequence of // Succ _type constructors_ applied to a Zero type constructor. // This function converts a value to a "type" // In a sense it is equivalent to a getInt function // that is present in some languages... template struct Peanoizer { typedef Succ::result> result; result type; }; struct Peanoizer<0> { typedef Zero result; result type; }; // The root module #define SHOWVAL(X) printf("The value of " #X " is: %d\n", X .eval()) // ARG1 is better defined as -DARG1 command-line option to the compiler #if !defined(ARG1) #define ARG1 4 #endif int main() { Succ > two; Succ one; printf("\nType arithmetics: computing bar(x) = (3*x+1)/(x-1)\n"); SHOWVAL( bar(two) ); SHOWVAL( bar(add(two,one)) ); SHOWVAL( Peanoizer().type ); SHOWVAL( bar( Peanoizer().type ) ); // SHOWVAL( bar(add(one,one)) ); // SHOWVAL( foo(two) ); // SHOWVAL( bar(two) ); // SHOWVAL( bar(add(two,one)) ); #if 0 // Old tests SHOWVAL( two ); SHOWVAL( neg(two) ); SHOWVAL( add(one,two) ); SHOWVAL( add(neg(one),two) ); SHOWVAL( add(neg(one),neg(two)) ); SHOWVAL( add(two,neg(two)) ); SHOWVAL( add(neg(two),neg(two)) ); SHOWVAL( add(neg(two),two) ); SHOWVAL( div(one,two) ); SHOWVAL( div(two,one) ); SHOWVAL( div(add(one,add(two,two)),add(div(two,two),one)) ); #if defined(TRYDIV0) SHOWVAL( div(add(two,two),add(div(two,two),neg(one))) ); #endif #endif }