如何在F#中实现“高效广义折叠”?

时间:2016-11-17 16:18:09

标签: f# nested polymorphism fold recursive-type

paper of Martin et al.中我读到了关于nestet数据类型的有效广义折叠。本文讨论Haskell,我想在F#中尝试。

到目前为止,我设法遵循Nest示例,包括gfold的实施。

type Pair<'a> = 'a * 'a
type Nest<'a> = Nil | Cons of 'a * Nest<Pair<'a>>

let example =
    Cons(1,
        Cons((2, 3),
            Cons(((4, 5), (6, 7)),
                Nil
            )
        )
    )

let pair (f:'a -> 'b) ((a, b):Pair<'a>) : Pair<'b> = f a, f b

let rec nest<'a, 'r> (f:'a -> 'r) : Nest<'a> -> Nest<'r> = function
    | Nil -> Nil
    | Cons(x, xs) -> Cons(f x, nest (pair f) xs)

//val gfold : e:'r -> f:('a * 'r -> 'r) -> g:(Pair<'a> -> 'a) -> _arg1:Nest<'a> -> 'r
let rec gfold e f g : Nest<'a> -> 'r = function
    | Nil -> e
    | Cons(x, xs) ->
        f(x, gfold e f g (nest g xs))

let uncurry f (a, b) = f a b

let up = uncurry (+)

let sum = example |> gfold 0 up up

不幸的是,gfold似乎有二次复杂性,这就是作者提出efold的原因。正如你可能猜到的那样,那是我无法工作的那个。在摆弄了许多类型的注释之后,我想出了这个版本只留下一个小小的波形:

let rec efold<'a, 'b, 'r> (e:'r) (f:'a * 'r -> 'r) (g:(Pair<'a> -> Pair<'a>) -> 'a -> 'a) (h:_) (nest:Nest<'a>) : 'r =
    match nest with
    | Nil -> e
    | Cons(x, xs) -> f(h x, efold e f g ((g << pair) h) xs)
                                                        ^^

唯一剩下的未指定类型是h。编译器推断val h : ('a -> 'a),但我认为需要有不同的类型。

提供的错误消息为

  

错误类型不匹配。期待着       巢&LT;&#39 a取代;
  但是给了一个       巢&LT;对&LT;&#39 a取代;&GT;
  在统一&#39; a&#39;和&#39;配对&#39; a&gt;&#39;

使用正确的h类型,错误应该消失。但我不太了解Haskell将其转换为F#。

另见this discussion关于论文中可能存在的拼写错误。

更新:这是我从kvb的回答中理解的:

所以h将输入类型转换为中间类型,就像在常规折叠中,累加器可以是不同类型的。然后使用g将两个中间类型值减少为1,而f获取中间类型和输入类型以生成输出类型值。当然e也是该输出类型。

h确实直接应用于递归期间遇到的值。另一方面,g仅用于使h适用于逐渐更深的类型。

只是看看第一个f示例,除了应用h和加速递归之外,它似乎没有做太多工作。但在复杂的方法中,我可以看到它是最重要的一个。什么出来,即它是工作马。

那是对的吗?

1 个答案:

答案 0 :(得分:6)

Haskell中efold的正确定义类似于:

efold :: forall n m b.
    (forall a. n a)->
    (forall a.(m a, n (Pair a)) -> n a)->
    (forall a.Pair (m a) -> m (Pair a))->
    (forall a.(a -> m b) -> Nest a -> n b) 
efold e f g h Nil = e 
efold e f g h (Cons (x,xs)) = f (h x, efold e f g (g . pair h) xs

这不能完全转换为F#,因为nm是&#34;更高级的类型&#34; - 它们是类型构造函数,在给定参数时创建类型 - 在F#中不支持(并且在.NET中没有干净的表示)。

解释

您的更新会询问如何解释折叠的参数。查看折叠如何工作的最简单方法可能是扩展将折叠应用于示例时会发生的情况。你会得到这样的东西:

efold e f g h example ≡
    f (h 1, f ((g << pair h) (2, 3), f ((g << pair (g << pair h)) ((4,5), (6,7)), e)))

因此h将值映射到可用作f的类型 第一个文章。 g用于将h应用于更深层嵌套的对(以便我们可以将h作为a -> m b类型的函数转换为Pair a -> m (Pair b)到{ {1}}等等,Pair (Pair a) -> m (Pair (Pair b))重复应用于脊柱,将f的结果与嵌套调用h的结果相结合。最后,f只使用一次,作为e最深层嵌套调用的种子。

我认为这种解释大多与你所推断的一致。 f对于组合不同层的结果无疑是至关重要的。但是f也很重要,因为它告诉你如何组合一个层中的各个部分(例如,当对节点求和时,它需要对左右嵌套总和求和;如果你想使用折叠来构建一个每个级别的值与输入值相反的新嵌套,您将使用g,其大致类似于g。)

简单方法

一种选择是为您关注的每个fun (a,b) -> b,aefold对创建n的专门实现。例如,如果我们想要总结m中包含的列表的长度,那么Nestn _将只是m _。我们可以稍微概括一下,intn _不依赖于他们的论点:

m _

另一方面,如果let rec efold<'n,'m,'a> (e:'n) (f:'m*'n->'n) (g:Pair<'m> -> 'm) (h:'a->'m) : Nest<'a> -> 'n = function | Nil -> e | Cons(x,xs) -> f (h x, efold e f g (g << (pair h)) xs) let total = efold 0 up up id example n确实使用了他们的参数,那么您需要定义一个单独的专门化(另外,您可能需要为每个多态创建新类型)参数,因为F#的较高 rank 类型的编码很尴尬。例如,要将嵌套的值收集到您希望m = n 'alist<'a> = m 'b的列表中。然后,我们可以观察到'b类型的唯一值是e,而不是为forall 'a.list<'a>的参数类型定义新类型,所以我们可以写:

[]

复杂的方法

虽然F#并不直接支持更高级别的类型,但事实证明可以以一种有些忠实的方式模拟它们。这是Higher库采用的方法。这是最小版本的样子。

我们创建了一个类型type ListIdF = abstract Apply : 'a * list<Pair<'a>> -> list<'a> type ListIdG = abstract Apply : Pair<'a> -> Pair<'a> let rec efold<'a,'b> (f:ListIdF) (g:ListIdG) (h:'a -> 'b) : Nest<'a> -> list<'b> = function | Nil -> [] | Cons(x,xs) -> f.Apply(h x, efold f g (pair h >> g.Apply) xs) let toList n = efold { new ListIdF with member __.Apply(a,l) = a::(List.collect (fun (x,y) -> [x;y]) l) } { new ListIdG with member __.Apply(p) = p } id n ,它将代表某个类型的应用程序App<'T,'a>,但我们将在哪里创建一个虚拟伴侣类型,可以作为T<'a>的第一个类型参数:

App<_,_>

现在我们可以为我们关心的类型构造函数定义一些伴随类型(这些类型构造函数通常可以存在于某些共享库中):

type App<'F, 'T>(token : 'F, value : obj) = 
    do
        if obj.ReferenceEquals(token, Unchecked.defaultof<'F>) then
            raise <| new System.InvalidOperationException("Invalid token")

    // Apply the secret token to have access to the encapsulated value
    member self.Apply(token' : 'F) : obj =
        if not (obj.ReferenceEquals(token, token')) then
            raise <| new System.InvalidOperationException("Invalid token")
        value 

现在我们可以一劳永逸地为有效折叠的参数定义更高级别的类型:

// App<Const<'a>, 'b> represents a value of type 'a (that is, ignores 'b)
type Const<'a> private () =
    static let token = Const ()
    static member Inj (value : 'a) =
        App<Const<'a>, 'b>(token, value)
    static member Prj (app : App<Const<'a>, 'b>) : 'a =
        app.Apply(token) :?> _

// App<List, 'a> represents list<'a>
type List private () = 
    static let token = List()
    static member Inj (value : 'a list) =
        App<List, 'a>(token, value)
    static member Prj (app : App<List, 'a>) : 'a list =
        app.Apply(token) :?> _

// App<Id, 'a> represents just a plain 'a
type Id private () =
    static let token = Id()
    static member Inj (value : 'a) =
        App<Id, 'a>(token, value)
    static member Prj (app : App<Id, 'a>) : 'a =
        app.Apply(token) :?> _

// App<Nest, 'a> represents a Nest<'a>
type Nest private () =
    static let token = Nest()
    static member Inj (value : Nest<'a>) =
        App<Nest, 'a>(token, value)
    static member Prj (app : App<Nest, 'a>) : Nest<'a> =
        app.Apply(token) :?> _

这样折叠只是:

// forall a. n a
type E<'N> =
    abstract Apply<'a> : unit -> App<'N,'a>

// forall a.(m a, n (Pair a)) -> n a)
type F<'M,'N> =
    abstract Apply<'a> : App<'M,'a> * App<'N,'a*'a> -> App<'N,'a>

// forall a.Pair (m a) -> m (Pair a))
type G<'M> =
    abstract Apply<'a> : App<'M,'a> * App<'M,'a> -> App<'M,'a*'a>

现在要致电let rec efold<'N,'M,'a,'b> (e:E<'N>) (f:F<'M,'N>) (g:G<'M>) (h:'a -> App<'M,'b>) : Nest<'a> -> App<'N,'b> = function | Nil -> e.Apply() | Cons(x,xs) -> f.Apply(h x, efold e f g (g.Apply << pair h) xs) ,我们需要对各种efoldInj方法进行调查,但其他方面看起来都像我们预期的那样:

Prj

希望这里的模式很清楚:在每个对象表达式中,应用程序方法都会抛出每个参数,对它们进行操作,然后将结果注入let toList n = efold { new E<_> with member __.Apply() = List.Inj [] } { new F<_,_> with member __.Apply(m,n) = Id.Prj m :: (n |> List.Prj |> List.collect (fun (x,y) -> [x;y])) |> List.Inj } { new G<_> with member __.Apply(m1,m2) = (Id.Prj m1, Id.Prj m2) |> Id.Inj } Id.Inj n |> List.Prj let sumElements n = efold { new E<_> with member __.Apply() = Const.Inj 0 } { new F<_,_> with member __.Apply(m,n) = Const.Prj m + Const.Prj n |> Const.Inj } { new G<_> with member __.Apply(m1,m2) = Const.Prj m1 + Const.Prj m2 |> Const.Inj } Const.Inj n |> Const.Prj let reverse n = efold { new E<_> with member __.Apply() = Nest.Inj Nil } { new F<_,_> with member __.Apply(m,n) = Cons(Id.Prj m, Nest.Prj n) |> Nest.Inj } { new G<_> with member __.Apply(m1,m2) = (Id.Prj 2, Id.Prj m1) |> Id.Inj } Id.Inj n |> Nest.Prj 类型。通过一些App<_,_>魔法,我们可以使这看起来更加一致(以少数类型注释为代价):

inline