Oct 052011
 

We know with static member constraints is possible to write more generic functions, duck typing is also possible.
In this series of posts I will show how combining static member constraints and operator resolution is possible to write code that otherwise is not possible with the current .Net Type System.

To illustrate this technique let’s think in an hypothetical situation where we want to implement an overloaded function that works with two different types, but we can’t change those type definitions.
So let’s create a function length : ‘a -> int and ‘a could be a list, an array or a string.

The first attempt may look like this:

type MyHelperType =
    static member length x = String.length x
    static member length x = List.length x
    static member length x = Array.length x

let length x = MyHelperType.length x

It doesn’t work, type inferred is

type MyHelperType =
  class
    static member length : x:string -> int
    static member length : x:'b list -> int
    static member length : x:'a [] -> int
  end
val length : string -> int

Although we can call MyHelperType.length [2;3;4] and MyHelperType.length “hello” we can’t get rid of the helper class at the calling site.
As we’ve seen before this is because .Net overloading is resolved at the calling site, so when we define length it picks up the first overload that match, in this case string.
Second try, we know those types have a Length property:

let inline length x = (^T: (member get_Length: unit -> int) (x))

Works, but we were lucky because we found a built-in method which exists in all those types.
However, we can’t always be that lucky, let’s try now to implement a function append (x:’a) (y:’a) where ‘a could be a string a list of chars or an array of chars.
If they were types we’re defining in the same module, no problem, we add a member cons then the rest is the same as previous solution.
Extension methods will not work, static constraints only look into the class definition.
Remember operators? They have a particular behavior, at operator resolution the compiler looks in every operand class, so for example if we have a binary operator $ : (‘a,’b) -> ‘c it looks first into class ‘a then into class ‘b for the operator definition.
So the trick is we will use an intermediary class with an operator with overloads for the second parameter.

type HelperType = HelperType with    
    static member (=>) (d:HelperType,x: list<char>) = fun y -> x @ y    
    static member (=>) (d:HelperType,x:array<char>) = fun y -> Array.append x y
    static member (=>) (d:HelperType,x:string     ) = fun y -> x + y

let inline append x = HelperType => x

The type inferred is

type HelperType =
  | HelperType
  with
    static member
      ( => ) : d:HelperType * x:char list -> (char list -> char list)
    static member
      ( => ) : d:HelperType * x:char array -> (char [] -> char [])
    static member ( => ) : d:HelperType * x:string -> (string -> string)
  end
val inline append :
   ^a ->  ^_arg3
    when (HelperType or  ^a) : (static member ( => ) : HelperType *  ^a ->
                                                            ^_arg3)

At this point we may wonder if it’s possible to write the same code without operators.
Just copy-paste the inferred type and try.

let inline append (x: ^a) :  ^_arg3 when (HelperType or  ^a) : (static member ( => ) : HelperType *  ^a ->  ^_arg3) = HelperType => x ;;

  let inline append (x: ^a) :  ^_arg3 when (HelperType or  ^a) : (static member ( => ) : HelperType *  ^a ->  ^_arg3) = HelperType => x ;;
  ------------------------------------------^^^^^^^^^^

stdin(3,43): error FS0010: Unexpected identifier in type constraint. Expected infix operator, quote symbol or other token.

It doesn’t work, I don’t know why, personally have no explanation why F# don’t accept the constraint itself infers.

Conclusion

As we’ve seen the trick consists in creating a type, specifically a discriminated union as intermediate type.
There we will implement the operators as static member, accepting an unused parameter which is a value of the DU intentionally to use an instance of that class later at the caller site, the other parameter is overloaded.
Then wrap with a function that will “hide” the DU and the operator.
This is very useful in those cases where we don’t have access to the source code of the type we’re overloading.
This way we can use inline to write more generic code, in future posts we will see more applications and more “tricks”.

  2 Responses to “Inline Fun Part I”

  1. I don’t understand how you would use your append inline function. You first started out trying to define a length function. But the various versions of length are defined in modules not classes. So how does your append function help?

    • Hi David,

      The end goal is to get a polymorphic function ready to use, so in the end it doesn’t matter whereas the individual functions for each type were in modules or classes, because once I have my function defined and compiled I will be able to use it directly.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)