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#.