How would I translate a Haskell type class into F#?

前端 未结 2 1010
臣服心动
臣服心动 2020-12-04 14:45

I\'m trying to translate the Haskell core library\'s Arrows into F# (I think it\'s a good exercise to understanding Arrows and F# better, and I might be able to use them in

相关标签:
2条回答
  • 2020-12-04 15:01

    Here's the approach I use to simulate Typeclasses (from http://code.google.com/p/fsharp-typeclasses/ ).

    In your case, for Arrows could be something like this:

    let inline i2 (a:^a,b:^b     ) =                                                      
        ((^a or ^b      ) : (static member instance: ^a* ^b     -> _) (a,b  ))
    let inline i3 (a:^a,b:^b,c:^c) =                                                          
        ((^a or ^b or ^c) : (static member instance: ^a* ^b* ^c -> _) (a,b,c))
    
    type T = T with
        static member inline instance (a:'a      ) = 
            fun x -> i2(a   , Unchecked.defaultof<'r>) x :'r
        static member inline instance (a:'a, b:'b) = 
            fun x -> i3(a, b, Unchecked.defaultof<'r>) x :'r
    
    
    type Return = Return with
        static member instance (_Monad:Return, _:option<'a>) = fun x -> Some x
        static member instance (_Monad:Return, _:list<'a>  ) = fun x  ->    [x]
        static member instance (_Monad:Return, _: 'r -> 'a ) = fun x _ ->    x
    let inline return' x = T.instance Return x
    
    type Bind = Bind with
        static member instance (_Monad:Bind, x:option<_>, _:option<'b>) = fun f -> 
            Option.bind  f x
        static member instance (_Monad:Bind, x:list<_>  , _:list<'b>  ) = fun f -> 
            List.collect f x
        static member instance (_Monad:Bind, f:'r->'a, _:'r->'b) = fun k r -> k (f r) r
    let inline (>>=) x (f:_->'R) : 'R = T.instance (Bind, x) f
    let inline (>=>) f g x    = f x >>= g
    
    type Kleisli<'a, 'm> = Kleisli of ('a -> 'm)
    let runKleisli (Kleisli f) = f
    
    type Id = Id with
        static member        instance (_Category:Id, _: 'r -> 'r     ) = fun () -> id
        static member inline instance (_Category:Id, _:Kleisli<'a,'b>) = fun () ->
            Kleisli return'
    let inline id'() = T.instance Id ()
    
    type Comp = Comp with
        static member        instance (_Category:Comp,         f, _) = (<<) f
        static member inline instance (_Category:Comp, Kleisli f, _) =
            fun (Kleisli g) -> Kleisli (g >=> f)
    
    let inline (<<<) f g = T.instance (Comp, f) g
    let inline (>>>) g f = T.instance (Comp, f) g
    
    type Arr = Arr with
        static member        instance (_Arrow:Arr, _: _ -> _) = fun (f:_->_) -> f
        static member inline instance (_Arrow:Arr, _:Kleisli<_,_>) = 
            fun f -> Kleisli (return' <<< f)
    let inline arr f = T.instance Arr f
    
    type First = First with
        static member        instance (_Arrow:First, f, _: 'a -> 'b) = 
            fun () (x,y) -> (f x, y)
        static member inline instance (_Arrow:First, Kleisli f, _:Kleisli<_,_>) =
            fun () -> Kleisli (fun (b,d) -> f b >>= fun c -> return' (c,d))
    let inline first f = T.instance (First, f) ()
    
    let inline second f = let swap (x,y) = (y,x) in arr swap >>> first f >>> arr swap
    let inline ( *** ) f g = first f >>> second g
    let inline ( &&& ) f g = arr (fun b -> (b,b)) >>> f *** g
    

    Usage:

    > let f = Kleisli (fun y -> [y;y*2;y*3]) <<< Kleisli ( fun x -> [ x + 3 ; x * 2 ] ) ;;
    val f : Kleisli<int,int list> = Kleisli <fun:f@4-14>
    
    > runKleisli f <| 5 ;;
    val it : int list = [8; 16; 24; 10; 20; 30]
    
    > (arr (fun y -> [y;y*2;y*3])) 3 ;;
    val it : int list = [3; 6; 9]
    
    > let (x:option<_>) = runKleisli (arr (fun y -> [y;y*2;y*3])) 2 ;;
    val x : int list option = Some [2; 4; 6]
    
    > ( (*) 100) *** ((+) 9)   <| (5,10) ;;
    val it : int * int = (500, 19)
    
    > ( (*) 100) &&& ((+) 9)   <| 5 ;;
    val it : int * int = (500, 14)
    
    > let x:List<_>  = (runKleisli (id'())) 5 ;;
    val x : List<int> = [5]
    

    Note: use id'() instead of id

    Update: you need F# 3.0 to compile this code, otherwise here's the F# 2.0 version.

    And here's a detailed explanation of this technique which is type-safe, extensible and as you can see works even with some Higher Kind Typeclasses.

    0 讨论(0)
  • 2020-12-04 15:04

    My brief answer is:

    OO is not powerful enough to replace type classes.

    The most straightforward translation is to pass a dictionary of operations, as in one typical typeclass implementation. That is if typeclass Foo defines three methods, then define a class/record type named Foo, and then change functions of

    Foo a => yadda -> yadda -> yadda
    

    to functions like

    Foo -> yadda -> yadda -> yadda
    

    and at each call site you know the concrete 'instance' to pass based on the type at the call-site.

    Here's a short example of what I mean:

    // typeclass
    type Showable<'a> = { show : 'a -> unit; showPretty : 'a -> unit } //'
    
    // instances
    let IntShowable = 
        { show = printfn "%d"; showPretty = (fun i -> printfn "pretty %d" i) }
    let StringShowable = 
        { show = printfn "%s"; showPretty = (fun s -> printfn "<<%s>>" s) }
    
    // function using typeclass constraint
    // Showable a => [a] -> ()
    let ShowAllPretty (s:Showable<'a>) l = //'
        l |> List.iter s.showPretty 
    
    // callsites
    ShowAllPretty IntShowable [1;2;3]
    ShowAllPretty StringShowable ["foo";"bar"]
    

    See also

    https://web.archive.org/web/20081017141728/http://blog.matthewdoig.com/?p=112

    0 讨论(0)
提交回复
热议问题