【问题标题】:Fold on non-lists?折叠非列表?
【发布时间】:2016-11-29 09:36:40
【问题描述】:

在最近的一项任务中,我被要求为一些非列表类型定义折叠函数。我仍然不能完全理解这个概念。到目前为止,我已经将fold 理解为执行list 中的后续元素。 fold on Tree 仍然具有直观意义,因为可以在根的子树上递归地应用某些函数。

但是,对于像这样的数据类型:

Maybe a :: Nothing | Just a

没有list(在我看来)可以执行折叠操作。

我确定我在理解这里的基本概念时遇到了一些问题,我将不胜感激。

【问题讨论】:

  • 提示:在某种程度上,Maybe a 可以被认为是 0 或 1 个元素的列表,fold 明确定义在每个元素之上东西。
  • 为了更明确,请注意任何Foldable 都可以转换为列表; toList <$> [Nothing, Just True] == [[], [True]]
  • 我想知道您实际上需要实现哪种“折叠”。在 Haskell 中,我们有 Foldable 类,其 foldr 相当于将数据类型列表化,然后在结果列表上使用 foldr。在其他上下文中,“折叠”是 消除器 的术语,如 cata 中的 recursion-schemes。在我的第一次 CS 考试中,我被要求在口试中产生“二叉树折叠”,这意味着类似于 cata

标签: haskell functional-programming


【解决方案1】:

Foldable 是一个非常令人困惑的类,老实说,因为它没有太多的法律,而且几乎可以为几乎任何给定类型编写很多不同的Foldable 实例。幸运的是,可以基于相同类型的 Traversable 实例(如果有的话)以纯机械方式确定 Foldable 实例应该做什么。

我们有

class (Functor t, Foldable t) => Traversable t where
  traverse :: Applicative f => (a -> f b) -> t a -> f (t b)

Traversable 有几个不同的定律,但事实证明最重要的一个是traverse Identity = Identity。让我们看看这如何适用于Maybe

traverse :: Applicative f => (a -> f b) -> Maybe a -> f (Maybe b)
traverse g Nothing = _none
traverse g (Just a) = _some

现在在第一种情况下,你需要产生f (Maybe b),而你只有g :: a -> f b。由于您没有任何 f 值,并且您没有任何 a 值,因此您唯一可以生成的是 pure Nothing

在第二种情况下,您必须产生f (Maybe b),并且您有g :: a -> f ba。因此,唯一有趣的开始方式是将g 应用于a,得到g a :: f b。现在你有两个选择要考虑:你可以丢弃这个值,只返回Nothing,或者你可以把它包裹在Just中。

根据身份法,traverse Identity (Just a) = Identity (Just a)。所以你不能返回Nothing唯一的合法定义是

traverse _ Nothing = pure Nothing
traverse g (Just a) = Just <$> g a

MaybeTraversable 实例完全由 Traversable 定律和参数决定。

现在可以使用traverse 折叠:

foldMapDefault :: (Traversable t, Monoid m)
               => (a -> m) -> t a -> m
foldMapDefault f xs =
  getConst (traverse (Const . f) xs)

因为这适用于Maybe

foldMapDefault f Nothing =
  getConst (traverse (Const . f) Nothing)
foldMapDefault f (Just a) =
  getConst (traverse (Const . f) (Just a))

扩展我们的定义,

foldMapDefault f Nothing = getConst (pure Nothing)
foldMapDefault f (Just a) = getConst (Just <$> (Const (f a)))

根据pure&lt;$&gt;Const 的定义,这些是

foldMapDefault f Nothing = getConst (Const mempty)
foldMapDefault f (Just a) = getConst (Const (f a))

解开构造函数,

foldMapDefault f Nothing = mempty
foldMapDefault f (Just a) = f a

这确实是为Maybe 定义foldMap 的方式。

【讨论】:

  • 这对于 OP 来说似乎有点高。无论如何,这是一个很好的答案,只是一些反馈。
【解决方案2】:

随着“基本概念”的发展,这非常令人费解,所以不要感觉太糟糕。

抛开你对折叠对列表的作用的直觉,考虑一下特定折叠函数(让我们使用foldr)如果应用于@987654322应该有什么type可能会有所帮助@。写List a 代替[a] 更清楚,列表上的标准foldr 具有类型:

foldr :: (a -> b -> b) -> b -> List a -> b

显然,Maybe 上的相应折叠必须具有类型:

foldrMaybe :: (a -> b -> b) -> b -> Maybe a -> b

考虑一下这可能有什么定义,因为它必须为所有ab 定义,而不知道关于类型的任何其他内容。作为进一步的提示,看看Data.Maybe 中是否已经定义了一个具有相似类型的函数——也许(哈哈)会给你一些想法。

【讨论】:

  • 我认为foldMaybe 应该具有这种类型远非显而易见——我会说b -&gt; (a -&gt; b) -&gt; Maybe a -&gt; b 更“明显”,因为foldList 的类型(几乎)对应于列表的自然消除器,b -&gt; (a -&gt; b) -&gt; Maybe a -&gt; bMaybe 的自然消除器 - 实际上在 Data.Maybe (maybe) 中有这种类型的函数。该类型没有foldMaybe,不包括Data.Foldable.foldr,它具有更通用的类型(并且此函数的类型与Maybe甚至List关系不大)
  • 好点,但要澄清一下,我的意思是很明显 foldr 应该具有与 Lists 类型明显相似的 Maybes 类型,不一定是最自然的折叠也许应该看起来像foldr。我已经编辑了答案以使其更清楚。事实上,Data.Foldable.foldr 在当前版本的 Haskell 中是 Prelude.foldr,并且它的类型确实以这种方式专门用于 Lists 和 Maybes,即使 foldr 不是 Maybe 的“自然”折叠。
猜你喜欢
  • 2018-12-24
  • 1970-01-01
  • 2016-03-14
  • 2017-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
  • 2012-02-25
相关资源
最近更新 更多