Looking at the details of F#’s duck typing

I’ve been playing around with duck typing a lot in F# lately and during a discussion at Lambda Days, I’ll take a look at how it actually works. The key point is whether reflection is used at all.

Duck typing is more properly known as structural typing. Normally we check if a type is an x whereas with duck typing we check to see whether it has a member y. It’s commonly called duck typing as we can say if it looks like a duck and quacks like a duck then it’s probably a duck. In the same way if a type W has member x and member y then something which has member x and member y is probably W.

In these examples I’ll be creating a function which takes a generic type ‘a and a string. It then calls the static function called Length on type ‘a and passes the string as an argument. The function definition looks like the following:

let inline GetLength< ^a when ^a :(static member Length:string -> int)> str =

The first thing you’ll notice is that the function is inline, so it will replace calls to the function with the actual content of the function. Next up we’ll need to actually call the Length function. So now our function looks like the following.

let inline GetLength< ^a when ^a : (static member Length : string -> int)> str =
    ( ^a : (static member Length : string -> int) str)

We’ll then create a type with the required members.

type A =
    let Length str =
        String.length str

And now we can call it from our Main function as follows.

[<EntryPoint>]
let main argv =
    let length = GetLength<A> "test"
    printfn "%i" length
    System.Console.ReadLine() |> ignore
    0

So now let’s open up our decompiler and see what it says. I’ll be using JetBrains dotPeek, I’ve found it to be pretty good in the past so let’s see what it says our GetLength function looks like under the hood.

GetLength

This could cause problems. Thankfully though we also have ILDASM as part of a visual studio install. Let’s open that up and take a look at the resulting IL.

GetLengthIL

So let’s look at this in detail. We create a local variable called length which is now the most recent item on the stack. We load 0 onto the stack. Then if the value at the top of the stack is 0, we branch to IL_000c. IL_000c simply throws a NotSupportedException with the message that “Dynamic invocation of Length is not supported”. Otherwise we load null onto the stack and unbox it into an integer. If it’s valid then we jump to the end. Store the value in local variable 0 (length), then read it back onto the stack and return it. Otherwise we throw the exception again. It’s pretty clear that this function will always throw an exception at the minute, so what happens then?

Remember back to our original function definition and how it was marked as being inline. That means the function call gets replaced in the place where it was called. So let’s now look at our Main where we call the function. As the function call doesn’t all fit on one line, I’ll add it below as text.


ExtraTopLevelOperators.PrintFormatLine<FSharpFunc<int, Unit>>((PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit>) new PrintfFormat<FSharpFunc<int, Unit>, TextWriter, Unit, Unit, int>("%i")).Invoke(Program.A.Length("test"));

Now look at the end, we can see a call to Program.A.Length with our string “test”. So that means we don’t need to worry about the performance hit we’d take by using any of the Reflection features. So our duck typing is actually just a compiler feature and so we can’t call it from C#.

… I am a fan of breaking things though, so let’s see if it fits with what we expect. When we create a very basic C# program to call this, we get the following:

CSFail

A NotSupportedException, just what we expected.

I hope this post has helped to clarify how duck typing works under the hood in F# and that there theoretically isn’t a difference in performance from regular function calls. (That’s a test for another post though).

Note: I’m far from an expert in IL, so if anybody is aware of any errors in my explanation then I’m more than happy to correct them. My reference to IL operators is available at http://en.wikipedia.org/wiki/List_of_CIL_instructions

One comment

Leave a comment