I've been very impressed with the FSharpPlus library, and how it allows for emulating some generic typeclasses. But I really believe in the power of separating a typeclass instance from the type definition, so I've been trying to build a proof-of-concept of doing some of the same techniques as FSharpPlus, but treating the typeclass instance as a completely separate entity. Honestly I'm stretching a bit beyond my SRTP abilities, and the FSharpPlus source is a little over my head. Here's my attempt so far:
module Monad =
let inline bind (m:^Monad) (f:'a -> '``Monad<'b>``) (x:'``Monad<'a>``) : '``Monad<'b>`` =
(^Monad : (member Bind : _*_ -> _) (m, f, x))
let inline rtn (m:^Monad) (source:'a) : '``Monad<'a>`` =
(^Monad : (member Return : _ -> _) (m, source))
type ListMonad = ListMonad with
member this.Bind (f, x) = List.collect f x
member this.Return x = [x]
type OptionMonad = OptionMonad with
member this.Bind (f, x) = Option.bind f x
member this.Return x = Some x
This allows me to do some basic stuff, but I'm hitting a wall as soon as I try to use bind twice in a single function:
let inline applyFromMonad (monadInstance) (f:'``Monad<'a -> 'b>) (x:'``Monad<'a>``) : '``Monad<'b>`` =
f |> Monad.bind monadInstance (fun f' ->
x |> Monad.bind monadInstance (fun a' ->
Monad.rtn monadInstance (f' a')))
(I'll worry about implementing computation expressions later). The problem is that the compiler is expecting f' and a' to be the same type, presumably because Monad.bind isn't being treated as generic enough. Instead of parameter f of the bind function being treated as a function accepting any generic type 'a, it's treating it as a function accepting some concrete type within the applyFromMonad function. Is there a way around this so that bind can be given arguments of different types when called twice?
Of course this whole point would be moot if this pull request makes it into the language, but I don't have enough insight to know if that's coming any time soon. Maybe my answer though would be to wait for this feature...
And of course, if there's another approach that will accomplish the same goal (typeclasses with a separate typeclass instance from the type definition, with signatures that would normally need higher-kinded types to properly define), I'd gladly consider something different.