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