Duck typing in F#

A sample to get duck typing in F#.

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

[fsharp]
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:

[fsharp]
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.

[fsharp]
> 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#.

Comments

1. On Monday, October 6 2008, 00:13 by vlad

I had second thought on subj,
Ducky vs derivation equal to structural vs nominal subtyping
Can you imagine operational semantic while having both
in F# - for example to use structural on interfaces (implements)
and nominal on derivation from class (extends).
Could be right stuff for OO part.