我如何将 Haskell 类型类转换为 F#? [英] How would I translate a Haskell type class into F#?

查看:25
本文介绍了我如何将 Haskell 类型类转换为 F#?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将 Haskell 核心库的 Arrows 翻译成 F#(我认为这是更好地理解 Arrows 和 F# 的一个很好的练习,我也许可以在我正在从事的项目中使用它们.)但是,由于范式的不同,直接翻译是不可能的.Haskell 使用类型类来表达这些东西,但我不确定什么 F# 构造最能将类型类的功能与 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 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#. 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.

对于 tl;dr 人群:我如何将类型类(Haskell 惯用语)翻译成 F# 惯用代码?

For the tl;dr crowd: How do I translate type-classes (a Haskell idiom) into F# idiomatic code?

对于那些接受我冗长解释的人:

For those accepting of my long explanation:

Haskell 标准库中的此代码是我尝试翻译的示例:

This code from the Haskell standard lib is an example of what I'm trying to translate:

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

尝试 1:模块、简单类型、Let 绑定

我的第一个尝试是直接使用用于组织的模块来简单地映射事物,例如:

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.

尝试 1a:使用签名和类型进行改进

纠正尝试 1 中某些问题的一种方法是使用 .fsi 文件来定义方法(因此类型执行起来更容易)并使用一些简单的类型调整来专门化.

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.

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

但是 fsi 文件不能重用于其他实现(以强制执行 let 绑定函数的类型),并且类型重命名/封装的东西很棘手.

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.

尝试 2:对象模型和接口

将 F# 也构建为面向对象的合理化,也许类型层次结构是正确的方法.

Rationalizing that F# is built to be OO also, maybe a type hierarchy is the right way to do this.

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"

除了让应该是静态方法(如 comp 和其他运算符)的行为像实例方法一样是多么痛苦之外,还需要显式向上转换结果.我也不确定这种方法是否仍然捕获了类型类多态性的完整表现力.这也使得很难使用必须是静态方法的东西.

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. 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.

尝试 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

啊,但这让我只能对所有类型的 IArrow 使用一种方法.如果我想要 ListArrows 稍微不同的 (&&&),我该怎么办?我还没有尝试过这种方法,但我想我可以隐藏 (&&&),或者至少提供一个更专业的版本,但我觉得我无法强制使用正确的变体.

Ah, but this locks me into using one method for all types of IArrow. If I wanted a slightly different (&&&) for ListArrows, what can I do? 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.

帮助我

那么我应该在这里做什么?我觉得 OO 应该足够强大来替换类型类,但我似乎无法弄清楚如何在 F# 中实现这一点.我的任何尝试是否接近?他们中的任何一个是否尽其所能"并且必须足够好?

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#. Were any of my attempts close? Are any of them "as good as it gets" and that'll have to be good enough?

推荐答案

这是我用来模拟 Typeclasses 的方法(来自 http://code.google.com/p/fsharp-typeclasses/).

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

用法:

> 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]

注意:使用 id'() 而不是 id

Note: use id'() instead of id

更新:您需要 F# 3.0 来编译此代码,否则 这里是 F# 2.0 版本.

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.

这篇关于我如何将 Haskell 类型类转换为 F#?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆