One language feature I like most in Haskell is type class. It was originally conceived as a way of implementing overloaded arithmetic and equality operators. It is a clever trick to support ad-hoc polymorphism, which does not require extensive modifications of the compiler or the type system.
F# does not provide type class, but we can emulate it with other F# language features such as operator overloading and inline function. Type Classes for F# shows this trick. Here’s the Functor example taken from the blog:
type Fmap = Fmap with
static member ($) (Fmap, x:option<_>) = fun f -> Option.map f x
static member ($) (Fmap, x:list<_> ) = fun f -> List.map f x
let inline fmap f x = Fmap $ x <| f
Here, fmap
function must be inline because inline functions can have statically resolved type parameters. Without the inline
modifier, type inference forces the function to take a specific type. In this case, the compiler can’t decide between option
and list
and emits an error.
You can use it as in the following:
> fmap ((+) 2) [1;2;3] ;;
val it : int list = [3; 4; 5]
> fmap ((+) 2) (Some 3) ;;
val it : int option = Some 5
I transliterated the TreeRec example of Simon Thompson’s paper, Higher-order + Polymorphic = Reusable into F# by emulating type class in this way.
type Tree<'a> =
| Leaf
| Node of 'a * Tree<'a> * Tree<'a>
type LeafClass = LeafClass with
static member ($) (LeafClass, t:'a list) = []
static member ($) (LeafClass, t:Tree<'a>) = Leaf
let inline leaf () : ^R = (LeafClass $ Unchecked.defaultof< ^R> )
type NodeClass = NodeClass with
static member ($) (NodeClass, l1:'a list) = fun a l2 -> List.concat [l1; [a]; l2]
static member ($) (NodeClass, t1:Tree<'a>) = fun a t2 -> Node(a, t1, t2)
let inline node a x1 x2 = (NodeClass $ x1) a x2
type TreeRecClass = TreeRecClass with
static member ($) (TreeRecClass, l:'a list) = fun f st ->
let listToTree = function
| [] -> failwith "listToTree"
| a::x ->
let n = List.length x / 2
let l1 = Seq.take n x |> Seq.toList
let l2 = Seq.skip n x |> Seq.toList
(a, l1, l2)
let rec treeRec' = function
| [] -> st
| l ->
let a, t1, t2 = listToTree l
let v1 = treeRec' t1
let v2 = treeRec' t2
f v1 v2 a t1 t2
treeRec' l
static member ($) (TreeRecClass, t:Tree<'a>) = fun f st ->
let rec treeRec' f st = function
| Leaf -> st
| Node(a, t1, t2) -> f (treeRec' f st t1) (treeRec' f st t2) a t1 t2
treeRec' f st t
let inline treeRec f st x = (TreeRecClass $ x) f st
let inline tSort x =
// FIXME: Implement sorting!
let mVal sort1 sort2 v = List.concat [sort1; sort2; [v]]
let mergeVal sort1 sort2 v t1 t2 = mVal sort1 sort2 v
treeRec mergeVal [] x
One problem with this approach is that we no longer can group related operations together into a single class. It can’t express that Tree-like type t* has three operations: leaf
, node
and treeRec
. We end up having three distinct types LeafClass
, NodeClass
and TreeRecClass
.