简体   繁体   English

我如何将Haskell类型类转换为F#?

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

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 a project I'm working on.) However, a direct translation isn't possible due to the difference in paradigms. 我正在尝试将Haskell核心库的Arrows转换为F#(我认为这对于更好地理解Arrows和F#是一个很好的练习,我可能能够在我正在开发的项目中使用它们。)但是,直接翻译由于范式的不同,这是不可能的。 Haskell uses type-classes to express this stuff, but I'm not sure what F# constructs best map the functionality of type-classes with the idioms of F#. Haskell使用类型类来表达这些东西,但是我不确定F#构造最好用F#的习语映射类型类的功能。 I have a few thoughts, but figured it best to bring it up here and see what was considered to be the closest in functionality. 我有一些想法,但最好把它提到这里,看看哪些被认为是最接近的功能。

For the tl;dr crowd: How do I translate type-classes (a Haskell idiom) into F# idiomatic code? 对于tl; dr crowd:我如何将类型类(一个Haskell成语)翻译成F#惯用代码?

For those accepting of my long explanation: 对于那些接受我长篇解释的人:

This code from the Haskell standard lib is an example of what I'm trying to translate: 来自Haskell标准库的代码是我正在尝试翻译的一个例子:

class Category cat where
    id :: cat a a
    comp :: cat a b -> cat b c -> cat a c
class Category a => Arrow a where
    arr :: (b -> c) -> a b c
    first :: a b c -> a (b,d) (c,d)

instance Category (->) where
    id f = f
instance Arrow (->) where
    arr f = f
    first f = f *** id

Attempt 1: Modules, Simple Types, Let Bindings 尝试1:模块,简单类型,让绑定

My first shot at this was to simply map things over directly using Modules for organization, like: 我的第一个镜头是直接使用模块组织来映射事物,例如:

type Arrow<'a,'b> = Arrow of ('a -> 'b)

let arr f = Arrow f
let first f = //some code that does the first op

That works, but it loses out on polymorphism, since I don't implement Categories and can't easily implement more specialized Arrows. 这是有效的,但它失去了多态性,因为我没有实现类别,也不能轻易实现更专业的箭头。

Attempt 1a: Refining using Signatures and types 尝试1a:使用签名和类型进行精炼

One way to correct some issues with Attempt 1 is to use a .fsi file to define the methods (so the types enforce easier) and to use some simple type tweaks to specialize. 纠正尝试1的一些问题的一种方法是使用.fsi文件来定义方法(因此类型强制执行更容易)并使用一些简单的类型调整来专门化。

type ListArrow<'a,'b> = Arrow<['a],['b]>
//or
type ListArrow<'a,'b> = LA of Arrow<['a],['b]>

But the fsi file can't be reused (to enforce the types of the let bound functions) for other implementations, and the type renaming/encapsulating stuff is tricky. 但是fsi文件不能被重用(以强制执行let绑定函数的类型)用于其他实现,并且类型重命名/封装的东西是棘手的。

Attempt 2: Object models and interfaces 尝试2:对象模型和接口

Rationalizing that F# is built to be OO also, maybe a type hierarchy is the right way to do this. 合理化F#也构建为OO,也许类型层次结构是正确的方法。

type IArrow<'a,'b> =
    abstract member comp : IArrow<'b,'c> -> IArrow<'a,'c>
type Arrow<'a,'b>(func:'a->'b) = 
    interface IArrow<'a,'b> with
        member this.comp = //fun code involving "Arrow (fun x-> workOn x) :> IArrow"

Aside from how much of a pain it can be to get what should be static methods (like comp and other operators) to act like instance methods, there's also the need to explicitly upcast the results. 除了可以将静态方法(如comp和其他运算符)用作实例方法之外,还有多少痛苦,还需要明确地重新生成结果。 I'm also not sure that this methodology is still capturing the full expressiveness of type-class polymorphism. 我也不确定这种方法是否仍在捕捉类型多态的完整表现力。 It also makes it hard to use things that MUST be static methods. 它也使得很难使用必须是静态方法的东西。

Attempt 2a: Refining using type extensions 尝试2a:使用类型扩展进行精炼

So one more potential refinement is to declare the interfaces as bare as possible, then use extension methods to add functionality to all implementing types. 因此,另一个可能的改进是尽可能地声明接口,然后使用扩展方法向所有实现类型添加功能。

type IArrow<'a,'b> with
    static member (&&&) f = //code to do the fanout operation

Ah, but this locks me into using one method for all types of IArrow. 啊,但这锁定了我对所有类型的IArrow使用一种方法。 If I wanted a slightly different (&&&) for ListArrows, what can I do? 如果我想要ListArrows略有不同(&&&),我该怎么办? I haven't tried this method yet, but I would guess I can shadow the (&&&), or at least provide a more specialized version, but I feel like I can't enforce the use of the correct variant. 我还没有尝试过这种方法,但我猜我可以隐藏(&&&),或者至少提供一个更专业的版本,但我觉得我不能强制使用正确的变体。

Help me 帮我

So what am I supposed to do here? 那我该怎么办呢? I feel like OO should be powerful enough to replace type-classes, but I can't seem to figure out how to make that happen in F#. 我觉得OO应该足够强大来替换类型类,但我似乎无法弄清楚如何在F#中实现这一点。 Were any of my attempts close? 我的任何尝试都关闭了吗? Are any of them "as good as it gets" and that'll have to be good enough? 他们中的任何一个“都是如此好”而且必须足够好吗?

My brief answer is: 我的简短回答是:

OO is not powerful enough to replace type classes. OO不足以替换类型类。

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定义了三个方法,那么定义一个名为Foo的类/记录类型,然后更改函数

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 https://web.archive.org/web/20081017141728/http://blog.matthewdoig.com/?p=112

Here's the approach I use to simulate Typeclasses (from http://code.google.com/p/fsharp-typeclasses/ ). 这是我用来模拟类型类的方法(来自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 注意:使用id'()而不是id

Update: you need F# 3.0 to compile this code, otherwise here's the F# 2.0 version . 更新:您需要F#3.0来编译此代码,否则这里是F#2.0版本

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. 这里是对这种技术的详细解释,它是类型安全的,可扩展的,你可以看到甚至可以使用一些更高级的类型。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM