【问题标题】:Type error when implementing finger trees实现手指树时的类型错误
【发布时间】:2016-10-04 13:55:57
【问题描述】:

我想玩 Hinze 的 (Haskell) 论文中描述的 2-3 根手指树(另请参阅 blog)。

type Node<'a> =
    | Node2 of 'a * 'a
    | Node3 of 'a * 'a * 'a

    static member OfList = function
        | [a; b] -> Node2(a, b)
        | [a; b; c] -> Node3(a, b, c)
        | _ -> failwith "Only lists of length 2 or 3 accepted!"

    member me.ToList () =
        match me with
        | Node2(a, b) -> [a; b]
        | Node3(a, b, c) -> [a; b; c]

type Digit<'a> =
    | One of 'a
    | Two of 'a * 'a
    | Three of 'a * 'a * 'a
    | Four of 'a * 'a * 'a * 'a

    static member OfList = function
        | [a] -> One(a)
        | [a; b] -> Two(a, b)
        | [a; b; c] -> Three(a, b, c)
        | [a; b; c; d] -> Four(a, b, c, d)
        | _ -> failwith "Only lists of length 1 to 4 accepted!"

    member me.ToList () =
        match me with
        | One a -> [a]
        | Two(a, b) -> [a; b]
        | Three(a, b, c) -> [a; b; c]
        | Four(a, b, c, d) -> [a; b; c; d]

    member me.Append x =
        match me with
        | One a -> Two(a, x)
        | Two(a, b) -> Three(a, b, x)
        | Three(a, b, c) -> Four(a, b, c, x)
        | _ -> failwith "Cannot prepend to Digit.Four!"

    member me.Prepend x =
        match me with
        | One a -> Two(x, a)
        | Two(a, b) -> Three(x, a, b)
        | Three(a, b, c) -> Four(x, a, b, c)
        | _ -> failwith "Cannot prepend to Digit.Four!"

[<NoComparison>]
[<NoEquality>]
type FingerTree<'a> =
    | Empty
    | Single of 'a
    | Deep of Digit<'a> * FingerTree<Node<'a>> * Digit<'a>

type Digit<'a> with
    member me.Promote () =
        match me with
        | One a -> Single a
        | Two(a, b) -> Deep(One a, Empty, One b)
        | Three(a, b, c) -> Deep(One a, Empty, Two(b, c))
        | Four(a, b, c, d) -> Deep(Two(a, b), Empty, Two(c, d))

type View<'a> = Nil | View of 'a * FingerTree<'a>

现在我无法让viewl 函数工作,它抱怨类型不匹配:

期待一个 FingerTree 但给定了一个 FingerTree>。

当统一 ''a' 和 'Node' FingerTree 时,生成的类型将是无限的。

let rec viewl : FingerTree<'a> -> View<'a> = function
    | Empty -> Nil
    | Single x -> View(x, Empty)
    | Deep(One x, deeper(*:FingerTree<'a>/FingerTree<Node<'a>>*), suffix) ->
        let rest =
            match viewl deeper with
            | Nil ->
                suffix.Promote()
            | View (node(*:Node<'a>*), rest) ->
                let prefix = node.ToList() |> Digit<_>.OfList
                Deep(prefix, rest, suffix)
        View(x, rest)
    | Deep(prefix, deeper, suffix) ->
        match prefix.ToList() with
        | x::xs ->
            View(x, Deep(Digit<_>.OfList xs, deeper, suffix))
        | _ -> failwith "Impossible!"

我之前在 prepend 中遇到过这个错误,但通过在函数中添加完整的类型信息能够解决它。

// These three/four type annotations solved the problem.
let rec prepend<'a> (a:'a) : FingerTree<'a> -> FingerTree<'a> = function
    | Empty -> Single a
    | Single b -> Deep(One a, Empty, One b)
    | Deep(Four(b, c, d, e), deeper, suffix) ->
        Deep(Two(a, b), prepend (Node3(c, d, e)) deeper, suffix)
    | Deep(prefix, deeper, suffix) ->
        Deep(prefix.Prepend a, deeper, suffix)

对于viewl,这似乎还不够,所以我还尝试在函数中间添加类型(查找 cmets)。没用。

我有点理解错误以及它的来源。谁能告诉我如何让这个工作?恕我直言,这应该是可能的,否则prepend 也不会编译。也许像this 这样的技巧有帮助? (虽然不明白)。


PS:我还把代码放在FsSnip 上,以便在浏览器中玩耍。

【问题讨论】:

  • 深第二项是FingerTree&lt;Node&lt;'a&gt;&gt;viewl 采用FingerTree&lt;'a&gt;,这意味着'a 必须是Node&lt;'a&gt;,因此它不能因此出现错误消息
  • @Sehnsucht:但是当删除任何prepend 的注释时,我得到了同样的错误。我敢打赌,同样的推理也适用,因为每个树级别都有自己的类型。但是对于 prepend,你可以让它工作。

标签: recursion types f# infinite finger-tree


【解决方案1】:

viewlprepend 等函数依赖于polymorphic recursion:对prepend 的递归调用采用与原始调用不同类型的参数。您可以在 F# 中定义此类函数,但您发现它们需要 full 类型注释(否则您会收到非常混乱的错误消息)。特别要注意,类型参数在函数定义中必须是显式的(尽管它们通常可以在调用点推断出来)。所以第一个问题是需要在定义中指定viewl&lt;'a&gt;

但是,还有一个非常微妙的第二个问题,它与Digit&lt;_&gt;.OfList 有关。尝试将第一段代码发送到 F# interactive 并查看生成的定义的签名:您将看到 static member OfList : (obj list -&gt; Digit&lt;obj&gt;),随后将导致无法正确定义 viewl。所以发生了什么事?您还没有给OfList 签名,所以它不会是一个通用方法(函数将被泛化,但成员永远不会被泛化)。但是编译器也无法推断出您打算将输入列表设为'a list 类型,其中'a 是该类型的泛型参数 - 为什么它会推断此特定类型而不是int liststring list, ETC。?所以它选择了一个无聊的单态默认值(obj list),除非你在后续代码中做一些事情来将它限制为不同的具体单态类型。相反,您需要向Digit 添加签名,然后一切都会好起来的。

通常在 F# 中,为每个类型创建一个单独的模块来定义相关函数(如 ToList 等)是惯用的。由于函数定义是通用的,这也可以避免您在此处遇到的 Digit 问题。也就是说,您可以像这样构建代码:

type Node<'a> =
    | Node2 of 'a * 'a
    | Node3 of 'a * 'a * 'a

module Node =
    let ofList = function
    | [a; b] -> Node2(a, b)
    | [a; b; c] -> Node3(a, b, c)
    | _ -> failwith "Only lists of length 2 or 3 accepted!"

    let toList = function
    | Node2(a, b) -> [a; b]
    | Node3(a, b, c) -> [a; b; c]

type Digit<'a> =
    | One of 'a
    | Two of 'a * 'a
    | Three of 'a * 'a * 'a
    | Four of 'a * 'a * 'a * 'a

[<NoComparison>]
[<NoEquality>]
type FingerTree<'a> =
    | Empty
    | Single of 'a
    | Deep of Digit<'a> * FingerTree<Node<'a>> * Digit<'a>

module Digit =
    let ofList = function
    | [a] -> One(a)
    | [a; b] -> Two(a, b)
    | [a; b; c] -> Three(a, b, c)
    | [a; b; c; d] -> Four(a, b, c, d)
    | _ -> failwith "Only lists of length 1 to 4 accepted!"

    let toList = function
    | One a -> [a]
    | Two(a, b) -> [a; b]
    | Three(a, b, c) -> [a; b; c]
    | Four(a, b, c, d) -> [a; b; c; d]

    let append x = function
    | One a -> Two(a, x)
    | Two(a, b) -> Three(a, b, x)
    | Three(a, b, c) -> Four(a, b, c, x)
    | _ -> failwith "Cannot prepend to Digit.Four!"

    let prepend x = function
    | One a -> Two(x, a)
    | Two(a, b) -> Three(x, a, b)
    | Three(a, b, c) -> Four(x, a, b, c)
    | _ -> failwith "Cannot prepend to Digit.Four!"

    let promote = function
    | One a -> Single a
    | Two(a, b) -> Deep(One a, Empty, One b)
    | Three(a, b, c) -> Deep(One a, Empty, Two(b, c))
    | Four(a, b, c, d) -> Deep(Two(a, b), Empty, Two(c, d))

type View<'a> = Nil | View of 'a * FingerTree<'a>

let rec viewl<'a> : FingerTree<'a> -> View<'a> = function
    | Empty -> Nil
    | Single x -> View(x, Empty)
    | Deep(One x, deeper, suffix) ->
        let rest =
            match viewl deeper with
            | Nil -> suffix |> Digit.promote
            | View (node, rest) ->
                let prefix = node |> Node.toList |> Digit.ofList
                Deep(prefix, rest, suffix)
        View(x, rest)
    | Deep(prefix, deeper, suffix) ->
        match prefix |> Digit.toList with
        | x::xs ->
            View(x, Deep(Digit.ofList xs, deeper, suffix))
        | _ -> failwith "Impossible!"

【讨论】:

  • 我正在路上,所以我无法尝试您的代码。只是一个关于多态递归的快速问题:这是不是用常规 F# 函数但用静态类方法不可能的事情?因为我试过这个技巧。
  • 不,函数和成员都可以使用多态递归,你只需要确保完全注释定义。您的代码按原样几乎完全没问题 - 只需进行我在前两段中提到的小改动即可。我将代码放在底部以显示更惯用的方法,但如果您对现有结构感到满意,则没有必要这样做。
  • 也感谢你提醒我类成员在类型推断方面的劣势。我知道 let-bound 函数更好,但这有点相反,也确实咬人。
猜你喜欢
  • 2018-07-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-12
  • 1970-01-01
  • 2017-07-21
  • 2021-05-22
相关资源
最近更新 更多