// compile with fsc numbers15.fs -r fractions.dll open System; let half = new rational(1,2); // keyword "new" is optional let third = rational(1,3); let quarter = rational(1,4); let threeqrts = half.add(quarter); Console.WriteLine(threeqrts); // invokes ToString function Console.WriteLine(rational(6,3));; /// a real number can be an integer, rational or a float type real = I of int | R of rational | D of float;; let h = R(rational(1,2)); // don't get confused with types here: let hi = I(4); // all of these values have type real. let hii = D(3.14);; (* A function to add any two reals may start with something like let rec addreals = function // why recursive? (swap parameters) | (I(x),I(y)) -> I(x+y) | (I(x),D(y)) -> D(float(x)+y) | (I(x),R(r)) -> R(r.add(rational(x,1))) | ... The important lesson here is that, in an equivalent, oop language setting, dynamic dispatch would fail to improve the code (triple dispatch would be required). We would have to resort to something like: real add(real a, real b) { if (a is Int and b is Int) return new Int(((Int)a).val + ((Int)b).val); else if (a is Int and b is Doub) return new Doub(((Int)a).val+((Doub)b).val); ... "is" checks the runtime type tag of an object in C# (=instanceof in java). But the biggest advantage to the ML approach is not the simplified syntax, although that's important (easier to debug), but STATIC type safety. You will discover that often when writing a ML/F# program, it will be correct if you can get it to type check. There's no need to type-cast, which compromises static type saftety in oop languages. If an if-clause is missing, it will not be a compiler error, but a missing or incorrect pattern will result in a compiler error. The closest that we've seen in oop is the visitor pattern (a missing visit will trigger an error), but that only allows double dispatch, which is not always enough. *) // function to convert real numbers to strings for printing let rtostring = function | I(x) -> string(x) | R(r) -> r.ToString() | D(z) -> string(z);; // sequence (list) of real numbers (non mutable) type rseq = Null | Node of (real*rseq);; let single x = Node(x,Null);; // print a rseq of numbers let rec printrseq = function | Null -> Console.WriteLine("") | Node(car,cdr) -> Console.Write(rtostring(car)+" "); printrseq cdr;; let m1 = Node(R(half),Node(D(3.14),Node(I(8),single(R(quarter)))));; printrseq m1;; // non-recursive length function: let length m = let mutable current = m let mutable stop = false let mutable cx = 0 while not(stop) do match current with | Null -> stop <- true | Node(_,cdr) -> cx <- cx+1; current <- cdr; cx;; // indentation used to end while loop, like in python // Not very efficient, works better with pointers (see below). // It's more natural to use tail recursion (more efficient than above): let length2 m = let rec len = function // local function inside length2 | (Null,cx) -> cx | (Node(_,cdr),cx) -> len(cdr,cx+1); // indentation ends current definition len(m,0);; printfn "length is %d" (length m1);; printfn "length2 is also %d" (length2 m1);; (* The following more advanced code use pointers to implement mutable lists (see append). *) let rgen = Random(); // for fun (nice to have all .Net available) // list of numbers, with mutable pointer: type rlist = Nil | Link of (real * (rlist ref));; // linked list of numbers. let rec printr = function | Nil -> printfn ""; | Link(I(x),next) -> (printf "%d " x; printr(!next)) | Link(D(x),next) -> (printf "%.2f " x; printr(!next)) | Link(R(x),next) -> (printf "%s " (x.ToString()); printr(!next));; // note extra () around x.ToString(). let rec append = function // destructively append to end of list | (r,Nil) -> ref (Link(r,ref Nil)) | (r,Link(h,({contents = Nil;} as next))) -> (next:= Link(r,ref Nil); next) | (r,Link(h,nextp)) -> append(r,!nextp);; // non-recursive version of append // expects ref rlist as second argument let appnd(r,rl) = let mutable current = rl in let mutable stop = false in while not(stop) do match !current with | Nil -> (current := Link(r,ref Nil); stop <- true) | Link(h,next) -> current <- next;; //appnd // note the difference between current := and current <-. current // is a pointer that can change what it's pointing to. := changes the // content of the pointed to structure // Syntax note: do not use tabs - just spaces - not sure why. // IMPORTANT NOTE: the non-recursive version of appnd is NOT more efficient // than the recursive append becuase the F# compiler recognizes TAIL // CALLS - i.e., the recursive call is always the very last thing executed. // This type of recursion (tail-recursion) is compiled into a loop. C does // this also, but not Java and C# (currently). let m = Link(R(rational(1,2)), ref (Link(I(3), ref Nil))); for i = 1 to 5 do ignore (append(I(rgen.Next(0,10)), m));; for i = 1 to 5 do // version using non-recursive version of appnd ignore (appnd(D(rgen.NextDouble()*10.0), ref m));; for i = 1 to 5 do let n,d = rgen.Next(0,10), rgen.Next(1,10); ignore (appnd(R(rational(n,d)), ref m));; printr m;; // the pattern {contents =Nil;} specifies a reference - it's not an assignment // statement but the description of a referenced pattern. // Another thing to watch out for: 3 + 2.1 will give error, it does not // auto-convert 3 to 3.0. Need float(3) + 2.1. F# is more strict when it // comes to types.