If it walks like a duck and quacks like a duck, I would call it a duck.
Some people love duck typing. I tend to prefer strong static inferred type systems, but dynamic typing can sometimes be nice. It is possible to get duck typing by using reflection in F#. I've written a small class to achieve this. October 2009 update: there is now an operator to achieve this more simply, you can read Dynamic lookup operator, aka duck typing in F#.
open System.Reflection type Duck(value: obj) = let mutable value = value let mutable ty = value.GetType() member d.Type = ty member d.Cast<'a>() = unbox<'a> value member d.Exec(str, args) = Duck (ty.InvokeMember(str, BindingFlags.GetProperty ||| BindingFlags.InvokeMethod, null, value, args)) member d.X(str) = d.Exec(str, [||]) member d.X(str, arg1) = d.Exec(str, [|box arg1|]) member d.X(str, arg1, arg2) = d.Exec(str, [|box arg1; box arg2|]) member d.Item with get str = d.Exec(str, [||]) member d.Val with get() = value and set x = value <- x; ty <- value.GetType()
Examples of use:
let a = Duck 42 a.Val <- "test" // change the value of a, it's now a string a.["Length"] // returns 4 a.X("Contains", "st") // execute a method - returns true let b = Duck [1; 2; 3] b.Val <- b.["Length"] b.Type.ToString() // returns "System.Int32" printfn "%d" (b.Cast()) // prints 3
The Cast method is used to get the typed value. b.Cast<int>() tries to return an int (run-time cast). Type annotation is not always needed thanks to type inference.
> let length d : int = (Duck d).["Length"].Cast();; val length : 'a -> int > length "test";; val it : int = 4 > length [1; 2];; val it : int = 2
Of course, all this code is unsafe: the methods names may be computed at run-time, may come from a user input or may also contain spelling mistakes. When you call the Cast method, you're coming back to the safe type checked world of F#.