【问题标题】:How would I translate a Haskell type class into F#?我如何将 Haskell 类型的类翻译成 F#?
【发布时间】:2011-05-01 09:24:58
【问题描述】:

我正在尝试将 Haskell 核心库的 Arrows 翻译成 F#(我认为这是更好地理解 Arrows 和 F# 的一个很好的练习,并且我可能能够在我正在从事的项目中使用它们。)但是,由于范式不同,无法直接翻译。 Haskell 使用类型类来表达这些东西,但我不确定哪种 F# 构造最能将类型类的功能与 F# 的习惯用法相映射。我有一些想法,但我认为最好将其提出来,看看在功能上什么被认为是最接近的。

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

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

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

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

我对此的第一个尝试是直接使用模块进行组织映射,例如:

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

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

这行得通,但它失去了多态性,因为我没有实现类别,也无法轻松实现更专业的箭头。

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

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

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

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

尝试 2:对象模型和接口

将 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"

除了让静态方法(如 comp 和其他运算符)像实例方法一样工作可能会带来多大的痛苦之外,还需要显式地向上转换结果。我也不确定这种方法是否仍能捕捉到类型类多态性的完整表现力。这也使得使用必须是静态方法的东西变得困难。

尝试 2a:使用类型扩展进行优化

因此,另一种可能的改进是尽可能地声明接口,然后使用扩展方法为所有实现类型添加功能。

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

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

帮帮我

那么我应该在这里做什么?我觉得 OO 应该足够强大以取代类型类,但我似乎无法弄清楚如何在 F# 中实现这一点。我的任何尝试都接近了吗?他们中的任何一个都“尽可能好”并且必须足够好吗?

【问题讨论】:

  • “我如何将类型类(Haskell 习惯用法)翻译成 F# 惯用代码?”馊主意。您应该查看您试图解决的问题,而不是碰巧使用类型类的解决方案,并弄清楚如何使用 F# 提供的功能来解决它​​。
  • @Jon Harrop 这就是问题的重点。 Haskell 用类型类解决了几十个问题,我想知道 F# 的替代方案是什么来解决类似的 class 问题。此外,Arrow 端口并不是为了解决任何问题,它只是一个“我认为了解更多关于 Arrows 的信息会很有趣”的活动。
  • 那么我认为如果你能解释一些类型类解决的问题并询问人们如何在 F# 中解决同样的问题,那将是非常有价值的。
  • @Gustavo 我很乐意换个答案(我很长一段时间都没有想过这个问题),但我并没有完全遵循你的代码。您的方法是否解决了第一个问题中提出的关于更高种类多态性的问题?就像我有一个包含 Monad 类型的 Arrow 一样,这一切都会奏效吗?
  • @CodexArcanum 是的,有点。推断的静态约束要求泛型类中存在一些静态方法。它适用于方法级别,因此它不仅适用于 Monad,而且适用于每个具有 Return 和 Bind 的类,最终它可能只需要 Return(如 Id 的情况)。

标签: oop haskell f# functional-programming typeclass


【解决方案1】:

我的简短回答是:

OO 功能不足以替代类型类。

最直接的翻译是传递一个操作字典,就像在一个典型的类型类实现中一样。也就是如果typeclassFoo定义了三个方法,那么定义一个名为Foo的类/记录类型,然后改函数

Foo a => yadda -> yadda -> yadda

类似的功能

Foo -> yadda -> yadda -> yadda

并且在每个呼叫站点,您都知道要根据呼叫站点的类型传递的具体“实例”。

这里有一个简短的例子来说明我的意思:

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

另见

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

【讨论】:

  • 另外,我觉得有趣的是,当我第一次接触 Haskell 时,我认为类型类是糟糕的接口,封装不好;现在我开始认为对象是糟糕的类型类,多态性很差。当我读到学习 FP 会毁掉你的 OOP 时,“他们”是对的。
  • 啊,我以前看过那篇博文,但没有好好阅读,我想是时候更正了。感谢您的链接。这里还有我提到的关于矩阵的博客文章:fdatamining.blogspot.com/2010/03/… 他还谈到了 INumerics 接口。是的,您的链接是正确的,如果您不能正确抽象出 Monad(和箭头)的基本操作,那么它们的用处就会大大降低。
  • 这种技术对于像Show 这样的类型类来说还不错。但是,当需要更高种类时,我还没有看到任何好的技术(例如,在原始问题中,cat 类似于 * =&gt; * =&gt; *,在 .NET 中无法清楚地表示)。
  • 我认为这种技术不适用于更高种类的类型。 .NET 类型系统不支持 Haskell 等更高种类的多态性。
  • 不,当您开始添加本身是泛型(更高种类)的类型时,Brian 提出的方法会很快失效。如果需要类型类多态性,似乎唯一的办法是字典查找方法,或者只是手动写出所有重复的内容以连接所有功能。
【解决方案2】:

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

在您的情况下,箭头可能是这样的:

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

更新:你需要 F# 3.0 来编译这段代码,否则here's the F# 2.0 version

还有here's 对这种技术的详细解释,该技术是类型安全的、可扩展的,并且您可以看到它甚至适用于某些更高类型的类型类。

【讨论】:

猜你喜欢
  • 2012-07-05
  • 1970-01-01
  • 1970-01-01
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-05
相关资源
最近更新 更多