Just a quick follow up on yesterdays post on Monoids.
Maybe you did not like the way the structure of the monoids was passed around using a simple record and indeed there is a way to define the Z
and ++
as generic operators by exploiting Statically Resolved Type Parameters in F#.
As far as I know this was first mentioned here and there is a great project on github using this FsControl.
Rewriting some of the instances this way can look something like this:
open FsCheck open FsCheck.Xunit type Neutral() = static member val Instance = Neutral() static member Neutral (_:list<_>, _:Neutral) = [] static member Neutral (_:'a->'a, _:Neutral) = id static member inline Invoke() = let inline neut (a:^a, b:^b) = ((^a or ^b):(static member Neutral:_*_->_) b, a) let inline neut' (a:'a) = neut (a, Unchecked.defaultof<'b>) :'b neut' Neutral.Instance type Operator() = static member val Instance = Operator() static member Op (a:list<_>, b:list<_>) = a @ b static member Op (f:'a->'a, g:'a->'a) = f >> g static member inline Invoke (a:'a) (b:'a) : 'a = let inline op (_:^M, x:^v, y:^v) = ((^M or ^v) : (static member Op:_*_->_) x, y) op (Operator.Instance, a, b) [<AutoOpen>] module Operators = let inline Z () = Neutral.Invoke () let inline (++) x y = Operator.Invoke x y type NonNullString = NNS of string with static member Neutral (_,_) = NNS "" static member Op (NNS a, NNS b) = NNS (a+b) type Prod = Prod of int with static member Neutral (_,_) = Prod 1 static member Op (Prod a, Prod b) = Prod (a*b) type Generators() = static member NonNullString : Arbitrary<NonNullString> = let nns s = if System.String.IsNullOrEmpty s then "" else s |> NNS { new Arbitrary<NonNullString>() with member __.Generator = Arb.generate |> Gen.map nns } module ``check Monoid axioms for int-Lists`` = type T = list<int> [<Property>] let ``[] is the neutral element``(v : T) = Z() ++ v = v && v ++ Z() = v [<Property>] let ``concatenation is commutative``(a : T, b : T, c : T) = a ++ (b ++ c) = (a ++ b) ++ c [<Xunit.ArbitraryAttribute(typeof<Generators>)>] module ``check Monoid axioms for NonNullString`` = type T = NonNullString [<Property>] let ``"" is the neutral element``(v : T) = Z() ++ v = v && v ++ Z() = v [<Property>] let ``concatenation is commutative`` (a : T, b : T, c : T) = a ++ (b ++ c) = (a ++ b) ++ c module ``check Monoid axioms for Prod`` = type T = Prod [<Property>] let ``1 is the neutral element``(v : T) = Z() ++ v = v && v ++ Z() = v [<Property>] let ``multiplication is commutative`` (a : T, b : T, c : T) = a ++ (b ++ c) = (a ++ b) ++ c
You can find a gist containing the relevant code here.
my take on it
First let me make clear that I love how those guys managed to put this all together! – this is a major hack of F# and it’s type-system. It’s one of the most clever thing I saw using F# and I am really impressed by all Gustavo did there.
But for me this is just to much boiler-plate only to get simulated type-class support that really should be there in the language itself (or maybe better: a ML-like module system). In the end at this point this is just used to get function-overloads based on type (a great feature). At the moment I prefer to use modules (like List.map
, Option.map
) – yes the compiler could know this but for me it’s no big deal.