【问题标题】:haskell fold operation on tree树上的haskell折叠操作
【发布时间】:2013-04-26 01:29:23
【问题描述】:
data Tree a = Tree a [Tree a]

请注意,我们不允许空树,叶子是具有空子树列表的树。

treeFold :: (a -> [b] -> b) -> Tree a -> b
treeFold f (Tree x s) = f x (map (treeFold f) s)

鉴于以上信息,我不明白树折叠功能如何 通过递归地对子树应用折叠操作来返回结果,然后将函数应用于根处的标签以及从子树返回的结果。

我也不明白 tree Fold 函数如何只接受一个参数而不是 2 个,当它作为参数传递给 map 函数并且它仍然可以正常编译和运行时。

例如下面的树大小函数,对树的节点进行计数。

treeSize :: Tree a -> Int
treeSize = treeFold (\x ys -> 1 + sum ys)

所以运行 treeSize tree 其中tree = Tree 4 [Tree 1 [Tree 2 [], Tree 3 []]] 将树的大小设为 4。

在上面的树大小函数中,树折叠函数也传递了一个参数而不是两个。此外,传递给树折叠函数的 x 并没有在任何地方使用,所以你为什么需要它。删除它会导致程序无法编译,并给出以下错误消息。

 Couldn't match type `a' with `[[Int] -> Int]'
      `a' is a rigid type variable bound by
          the type signature for treeSize :: Tree a -> Int
          at treeFold.hs:15:1
    In the first argument of `sum', namely `ys'
    In the second argument of `(+)', namely `sum ys'
    In the expression: 1 + sum ys

任何帮助将不胜感激。

【问题讨论】:

标签: haskell tree fold


【解决方案1】:

未使用的参数

首先,将变量显式标记为未使用的方法是将变量替换为_。所以你真的想要:

treeFold (\_ ys -> 1 + sum ys)

因为你写了一个编译器错误:

treeFold (\ys -> 1 + sum ys)

...这不是一回事。

折叠

其次,我将在示例树上手动评估 treeSize,以便您可以看到没有魔法发生:

treeSize (Tree 1 [Tree 2 [], Tree 3 []])

-- Inline definition of 'treeSize'
= treeFold (\_ ys -> 1 + sum ys) (Tree 1 [Tree 2 [], Tree 3 []])

-- Evaluate treeFold
= (\_ ys -> 1 + sum ys) 1 (map (treeFold (\_ ys -> 1 + sum ys)) [Tree 2 [], Tree 3 []])

-- Apply the anonymous function
= 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [Tree 2 [], Tree 3 []])

-- Apply the 'map' function
= 1 + sum [ treeFold (\_ ys -> 1 + sum ys) (Tree 2 [])
          , treeFold (\_ ys -> 1 + sum ys) (Tree 3 [])
          ]

-- Apply both 'treeFold' functions
= 1 + sum [ (\_ ys -> 1 + sum ys) 2 (map (treeFold (\_ ys -> 1 + sum ys)) [])
          , (\_ ys -> 1 + sum ys) 3 (map (treeFold (\_ ys -> 1 + sum ys)) [])
          ]

-- Apply the anonymous functions
= 1 + sum [ 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [])
          , 1 + sum (map (treeFold (\_ ys -> 1 + sum ys)) [])
          ]

-- map f [] = []
= 1 + sum [ 1 + sum []
          , 1 + sum []
          ]

-- sum [] = 0
= 1 + sum [1 + 0, 1 + 0]
= 1 + sum [1, 1]

-- Apply 'sum'
= 1 + 2
= 3

但是,有一种简单的方法可以记住 treeFold 的工作原理。它所做的一切都是用您提供的函数替换每个 Tree 构造函数。

如果你有:

Tree 1 [Tree 2 [Tree 3 [], Tree 4[]], Tree 5 [Tree 6 [], Tree 7 []]]

...然后您将treeFold f 应用于此,它将评估为:

f 1 [f 2 [f 3 [], f 4 []], f 5 [f 6 [], f 7 []]]

treeSum 只是f = (\_ ys -> 1 + sum ys) 的特例:

1 + sum [1 + sum [1 + sum [], 1 + sum []], 1 + sum [1 + sum [], 1 + sum []]]

= 7

柯里化

最后一点是在 Haskell 中柯里化是如何工作的。当你定义一个函数时:

foo x y = x + y

...编译器实际上将其脱糖为两个嵌套函数:

foo = \x -> (\y -> x + y)

这就是为什么您可以在 Haskell 中仅将函数部分应用于一个参数。当你写foo 1时,它会翻译成:

foo 1

= (\x -> (\y -> x + y)) 1

= \y -> 1 + y

换句话说,它返回一个有一个参数的新函数。

在 Haskell 中,所有函数都只接受一个参数,我们通过返回新函数来模拟多个参数的函数。这种技术被称为“currying”。

如果您更喜欢更传统的主流语言的多参数方法,您可以通过让函数接受元组参数来模拟它:

f (x, y) = x + y

但是,这并不是真正惯用的 Haskell,它不会给您带来任何形式的性能改进。

【讨论】:

    【解决方案2】:

    第一个问题有点棘手,因为这就是递归......正如老师所说:“要理解递归,你必须学习递归是如何工作的”。 :-P 一个小建议:尝试通过treeFold 的应用程序使用一棵树或一棵树内有一棵树,然后自己评估它(在纸上左右)。我想,那么你可以对正在发生的事情有所了解......(当然使用一个简单的函数作为 treeFold 的参数;就像你的 treeSize 使用的那样)。

    treeFold在映射体中只得到一个参数,因为map 需要来自a->b 的函数,而treeFold 的类型为(a -> [b] -> b) -> Tree a -> b.,所以如果你要传递2 个参数,你会传递到map 只是一个值,这会导致失败。 (一个可以理解的例子:(+)需要两个参数。如果你写map (+1) [1,2,3],你会得到[2,3,4] ,因为 (+1) 应用于列表中的每个元素(并且 (+1) 显然还需要一个参数,就像您上面的 treeFold f 一样)

    您的示例 treeSize : 当你说它只有一个论点时,这是不对的。你可以写

    treeSize t = treeFold (\x ys -> 1 + sum ys) t
    

    而不是您上面的定义。 不使用 x 是因为计算它是无用的。但是,treeFold 需要 有一个函数,它需要两个参数,所以你给它 x。这是唯一的原因。您可以传递任何其他值。

    【讨论】:

    • 我尝试过使用单个 Tree 浏览 treeFold 但仍然没有得到它。如果你有一个名为add 的函数,它把两个数字相加,那么你可以写成add x y = x + y。当您将 add 传递给 map 作为其第一个参数时,如 map (add 5) [1,2,3],它返回 [6,7,8]。但是你只传入一个参数来添加。它从哪里得到另一个论点?所以 x 和 y 是传递给 treeFold 的两个参数。
    • @Ishan:写成map (\x -> add 5 x) [1, 2, 3] 可能会更清楚。但随后\x -> add 5 xadd 5 通过eta reduction 相同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-04
    • 1970-01-01
    • 1970-01-01
    • 2015-08-30
    • 1970-01-01
    相关资源
    最近更新 更多