【问题标题】:What's the relation of fold on Option, Either etc and fold on Traversable?Option、Either 等上的 fold 和 Traversable 上的 fold 有什么关系?
【发布时间】:2011-12-16 20:48:50
【问题描述】:

Scalaz 为各种 ADT 提供了一个名为 fold 的方法,例如 BooleanOption[_]Validation[_, _]Either[_, _] 等。该方法基本上采用与给定 ADT 的所有可能情况相对应的函数。换句话说,如下所示的模式匹配:

x match {
  case Case1(a, b, c) => f(a, b, c)
  case Case2(a, b) => g(a, b)
  .
  .
  case CaseN => z
}

相当于:

x.fold(f, g, ..., z)

一些例子:

scala> (9 == 8).fold("foo", "bar")
res0: java.lang.String = bar

scala> 5.some.fold(2 *, 2)
res1: Int = 10

scala> 5.left[String].fold(2 +, "[" +)
res2: Any = 7

scala> 5.fail[String].fold(2 +, "[" +)
res6: Any = 7

同时,Traversable[_]类型有一个同名操作,它遍历集合对其元素执行一定的操作,并累加结果值。例如,

scala> List(2, 90, 11).foldLeft("Contents: ")(_ + _.toString + " ")
res9: java.lang.String = "Contents: 2 90 11 "

scala> List(2, 90, 11).fold(0)(_ + _)
res10: Int = 103

scala> List(2, 90, 11).fold(1)(_ * _)
res11: Int = 1980

为什么这两个操作使用相同的名称 - fold/catamorphism?我看不出两者之间有任何相似之处/关系。我错过了什么?

【问题讨论】:

    标签: scala functional-programming scalaz category-theory catamorphism


    【解决方案1】:

    我认为你遇到的问题是你看到这些东西是基于它们的实现,而不是它们的类型。考虑一下这种简单的类型表示:

    List[A] = Nil 
            | Cons head: A tail: List[A]
    
    Option[A] = None
              | Some el: A
    

    现在,让我们考虑Option 的弃牌:

    fold[B] = (noneCase: => B, someCase: A => B) => B
    

    因此,在Option 上,它将所有可能的情况减少到B 中的某个值,然后返回。现在,让我们看看List 的相同情况:

    fold[B] = (nilCase: => B, consCase: (A, List[A]) => B) => B
    

    但是请注意,我们在List[A] 上进行了递归调用。我们必须以某种方式折叠它,但我们知道 List[A] 上的 fold[B] 总是会返回 B,所以我们可以这样重写它:

    fold[B] = (nilCase: => B, consCase: (A, B) => B) => B
    

    换句话说,我们将List[A] 替换为B,因为在给定fold 的类型签名的情况下,折叠它总是会返回B。现在,让我们看看 foldRight 的 Scala(用例)类型签名:

    foldRight[B](z: B)(f: (A, B) ⇒ B): B
    

    说,这让你想起了什么吗?

    【讨论】:

    • 哦,所以它必须递归应用!有道理,谢谢。
    • The Wikipedia page on "catamorphism" 说,“在函数式编程中,变态是从函数式编程已知的列表上的折叠泛化到可以描述为初始代数的任意代数数据类型。”然后它指向 Erik Meijer 的论文“使用香蕉、透镜、信封和铁丝网进行函数式编程”。我认为我应该阅读那篇论文以更好地理解该主题。
    • @missingfaktor,在 wikipedia 页面的末尾有 6 部分关于 catamorphism 的博客,看起来很容易访问。我正在读它。它是 F#,但我相信这对你来说不是问题。
    • @huynhjl:哦,我最初错过了。谢谢你指点我!
    【解决方案2】:

    如果您将“折叠”视为“通过操作将容器中的所有值以种子值压缩”,并且您将 Option 视为最多可以具有一个值的容器,那么这就开始了说得通。

    事实上,foldLeft 具有相同的签名,如果您在空列表 vs None 以及只有一个元素 vs Some 的列表上使用它,则给出完全相同的结果:

    scala> val opt : Option[Int] = Some(10)
    opt: Option[Int] = Some(10)
    
    scala> val lst : List[Int] = List(10)
    lst: List[Int] = List(10)
    
    scala> opt.foldLeft(1)((a, b) => a + b)
    res11: Int = 11
    
    scala> lst.foldLeft(1)((a, b) => a + b)
    res12: Int = 11
    

    fold 也在 Scala 标准库中的 ListOption 上定义,具有相同的签名(我相信它们都继承自一个 trait,事实上)。再一次,您在单例列表上获得与在 Some 上相同的结果:

    scala> opt.fold(1)((a, b) => a * b)
    res25: Int = 10
    
    scala> lst.fold(1)((a, b) => a * b)
    res26: Int = 10
    

    我不是 100% 确定来自 Scalaz 在Option/Either/etc 上的fold,你提出了一个很好的观点。它似乎与我习惯的“折叠”有很大不同的签名和操作。

    【讨论】:

      猜你喜欢
      • 2011-07-16
      • 2015-12-17
      • 1970-01-01
      • 1970-01-01
      • 2010-11-29
      • 2014-07-06
      • 2015-03-30
      • 1970-01-01
      • 2020-07-05
      相关资源
      最近更新 更多