Typeclassess are achived through abstract classes, which makes it works perfect for both subtypeclassing and default implementations.
If some type is constructed with a type constructor, you can implement show class for it.
Let's have a look at how to write a show class and use it in polymorphism functions and even operators.
open FSTan.HKT
// define a simple typeclass
[<AbstractClass>]
type show<'s>() =
abstract member show<'a> : hkt<'s, 'a> -> string
// I have a typeclass,
// I have 2 datatypes,
// Oh!
// Polymorphism!
let show<'a, 's when 's :> show<'s>> = getsig<'s>.show<'a>
type myData1<'a> = // define datatype
| A | B | C
interface hkt<MyTypeCons1, 'a>
and MyTypeCons1() =
// define type constructor
// in F#, we don't really have this, but
// we can leverage a signature type(yes, this is just a signature)
// and `hkt`(check FSTan.HKT, not magic at all)
// to fully simulate a type constructor.
inherit show<MyTypeCons1>() with
override si.show a =
// This conversion can absolutely succeed
// for there is only one datatype which
// interfaces hkt<MyTypeCons1, 'a>
let a = a :?> _ myData1
sprintf "%A" a
type myData2<'a> = // define datatype
| I of int
| S of string
interface hkt<MyTypeCons2, 'a>
and MyTypeCons2() =
// define type constructor
// in F#, we don't really have this, but
// we can leverage a signature type(yes, this is just a signature)
// and `hkt`(check FSTan.HKT, not magic at all)
// to fully simulate a type constructor.
inherit show<MyTypeCons2>() with
override si.show a =
let a = a :?> _ myData2
match a with
| I a -> sprintf "isInt %d" a
| S a -> sprintf "isStr %s" a
let test() =
let s1 = show <| I 32
let s2 = show <| S "123"
let s3 = show A
let s4 = show B
printfn "%s\n%s\n%s\n%s" s1 s2 s3 s4
Output:
isInt 32
isStr 123
A
B
If you're familiar with Haskell and related stuffs, you must have an experience with this case:
class Applicative m => Monad m where
-- descriptions of monad typeclassAbove code descibes the an example of dependencies between typeclasses, and Monad
is exactly a subtypeclass of Applicative.
After introducing this sort of constraints, a higher level abstraction is achieved, which enables reaching a higher ratio of code reuse.
For instance, a Monad instance requires implementations of many specific methods(return, bind, combine and so on), but we already have a knowledge about subtypeclassing information of Monad, it's a subtypeclass of Functor and Applicative, so if we implement pure from Applicative and implement bind from Monad for an instance(eg. Maybe) of Monad, we then implement all instances of Functor, Applicative and Monad.
In F#, we can implement Either Monad(also Functor and Applicative).
open FSTan.HKT
open FSTan.Show
open FSTan.Monad
type either<'e, 'a> = hkt<EitherMonad<'e>, 'a>
and EitherMonad<'e>() =
inherit monad<EitherMonad<'e>>() with
override __.pure'<'a> (a: 'a): either<'e, 'a> = Right a :> _
override __.bind<'a, 'b> (m: either<'e, 'a>) (k: 'a -> either<'e, 'b>): either<'e, 'b> =
match m :?> eitherData<'e, 'a> with
| Left l -> Left l :> _
| Right r -> k r
interface show<EitherMonad<'e>> with
member __.show<'a> (a: either<'e, 'a>) =
let a = a :?> eitherData<_, _>
a.ToString()
and eitherData<'e, 'a> =
| Left of 'e
| Right of 'a
interface either<'e, 'a>
let Left<'e, 'a> (e: 'e) : either<'e, 'a> = Left e :> _
let Right<'e, 'a> (a: 'a) : either<'e, 'a> = Right a :> _
let (|Left|Right|) (m: either<'e, 'a>) =
match m :?> eitherData<'e, 'a> with
| Left l -> Left l
| Right r -> Right rAbobe codes in Haskell is similar to
data Either e a = Left e | Right a
instance Functor (Either e) where
-- ... `fmap` and other "methods" are automatically implemented by `bind` and `return`.
instance Applicative (Either e) where
pure = Right
-- ...
instance Monad (Either e) where
return = pure
bind m k = \case
Left l -> Left l
Right r -> k r
instance Show (Either e a) where
-- this section is not that similar to haskellFeel free to check implementation of Monad in FSTan.
let test_hkt<'a, 'b, 'c> (f: hkt<'a, 'b>) : hkt<'b, 'c> =
/// implIn terms of above snippet, if c is a concrete type, then 'a has kind * -> * -> *, as well as b has kind * -> *.
Open FSTan.Monad, where a computation expression is provided to be an alternative of do in Haskell.
open FSTan.Monad
open FSTan.Control.Trans.State
open FSTan.Data.Maybe
open FSTan.Data.Either
let plusOne<'m when 'm :> monad<'m>> : stateT<int, 'm, unit> = Do {
let! state = get // similar to `state <- get` in haskell
do! put <| state + 1
return ()
}
let valueMaybe : stateT<int, MaybeSig, unit> = plusOne
// type maybe<'a> = hkt<MaybeSig, 'a>
let valueEither<'e> : stateT<int, EitherSig<'e>, unit> = plusOne
// type either<'e, 'a> = hkt<EitherSig<'e>, 'a>