我正在尝试将Haskell核心库的Arrows转换为F#(我认为这对于更好地理解Arrows和F#是一个很好的练习,我可能能够在我正在开发的项目中使用它们。)但是,由于范式的不同,不可能直接翻译。 Haskell使用类型类来表达这些东西,但是我不确定F#构造最好用F#的习语映射类型类的功能。我有一些想法,但最好把它放在这里,看看被认为是最接近功能的。
对于tl; dr crowd:如何将类型类(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稍微不同(&amp;&amp;&amp;),我该怎么办?我还没有尝试过这种方法,但我猜我可以隐藏(&amp;&amp;&amp;&amp;),或者至少提供一个更专业的版本,但我觉得我不能强制使用正确的变体。
帮帮我
那我该怎么办呢?我觉得OO应该足够强大来替换类型类,但我似乎无法弄清楚如何在F#中实现这一点。我的任何尝试都关闭了吗?他们中的任何一个“一切都好”并且必须足够好吗?
答案 0 :(得分:30)
我的简短回答是:
OO不足以替换类型类。
最直接的翻译是传递操作字典,就像在一个典型的类型类实现中一样。也就是说,如果类型类Foo
定义了三个方法,那么定义一个名为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
答案 1 :(得分:22)
这是我用来模拟类型类的方法(来自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这个技术的详细解释是类型安全的,可扩展的,你可以看到甚至可以使用更高类型的类型。