flink fold_Option.fold()被认为不可读

flink fold

最近我们在代码审查期间进行了长时间的讨论,不管scala.Option.fold()是惯用的还是聪明的,或者是scala.Option.fold()和棘手的? 让我们首先描述问题所在。 Option.fold有两件事:将函数f映射到Option的值(如果有)上,或者在不存在的情况下返回替代alt 。 使用简单的模式匹配,我们可以如下实现它:



val option: Option[T] = //...
def alt: R = //...
def f(in: T): R = //...
 
val x: R = option match {
    case Some(v) => f(v)
    case None => alt
}

如果您喜欢getOrElsefold实际上是mapgetOrElse的组合:

val x: R = option map f getOrElse alt

或者,如果您仍然是C程序员,但仍想使用Cala编写Scala编译器,请执行以下操作:

val x: R = if (option.isDefined)
    f(option.get)
else
    alt

有趣的是,这类似于fold()的实际实现方式,但这是一个实现细节。 好的,以上所有内容都可以用单个Option.fold()代替:

val x: R = option.fold(alt)(f)

从技术上讲,您甚至可以使用/:\:运算符( alt /: option )–但这只是受虐狂。 我在使用option.fold()惯用语时遇到三个问题。 首先–除了可读性之外,什么都没有。 我们正在折叠(减少) Option –的确没有多大意义。 其次,它通过以失​​败(不存在, alt )条件开始,然后跟随状态块( f函数;另请参见:将map-getOrElse重构为fold逆转普通的正则负情况 。 有趣的是,如果将其命名为mapOrElse则该方法对我来说非常mapOrElse

/**
 * Hypothetical in Option
 */
def mapOrElse[B](f: A => B, alt: => B): B =
    this map f getOrElse alt

实际上,Scalaz中已经有这样的方法,称为OptionW.cata卡塔 这是马丁·奥德斯基(Martin Odersky)所说的

“我个人发现像cata这样的方法需要两个闭包,因为参数通常会夸大它。 您真的通过map + getOrElse获得了可读性吗? 想一想您的代码的新手[…]”

尽管cata具有一定的理论背景 ,但Option.fold听起来像是随机的名称冲突,除了混乱之外,什么都没有带来。 我知道您会说什么, TraversableOnce已经fold ,我们正在做同样的事情。 为什么是随机冲突而不是扩展TraversableOnce描述的协定? Scala集合中的fold()方法通常仅委托foldLeft() / foldRight() (对于给定的数据结构更好地工作)之一,因此它不能保证顺序,并且折叠功能必须是关联的。 但是在Option.fold() ,契约是不同的:折叠函数只接受一个参数,而不是两个。 如果您阅读我以前的关于折痕的文章,则知道折减函数始终采用两个参数:当前元素和累加值(第一次迭代期间的初始值)。 但是Option.fold()仅接受一个参数:当前Option值! 这破坏了一致性,尤其是当实现Option.foldLeft()Option.foldRight()具有正确的协定时(但这并不意味着它们更具可读性)。

理解option折叠的唯一方法是将Option想象为具有01元素的序列。 那有点道理,对吗? 没有。

def double(x: Int) = x * 2
 
Some(21).fold(-1)(double)   //OK: 42
None.fold(-1)(double)       //OK: -1

但:

Some(21).toList.fold(-1)(double)
<console>: error: type mismatch;
 found   : Int => Int
 required: (Int, Int) => Int
              Some(21).toList.fold(-1)(double)
                                       ^

如果我们将Option[T]视为List[T] ,则尴尬的Option.fold()会中断,因为它的类型与TraversableOnce.fold() 。 这是我最大的担心。 我不明白为什么不按类型系统(特征?)定义折叠并严格执行。 作为一个例子看看:

在Haskell中

Data.Foldable类型类描述Haskell中的各种折叠Data.Foldable 。 在Scala中有熟悉的foldl / foldr / foldl1 / foldr1 ,因此命名为foldLeft / foldRight / reduceLeft / reduceRight 。 它们具有与Scala相同的类型,并且对所有可以折叠的类型(包括Maybe ,列表,数组等)的行为都不足为奇。还有一个名为fold的函数,但它的含义完全不同:

class Foldable t where
    fold :: Monoid m => t m -> m

尽管其他折叠非常复杂,但此折叠几乎不带一个m s的可折叠容器(必须为Monoid s)并返回相同的Monoid类型。 快速回顾:如果类型存在一个中性值,并且一个操作使用两个值而只产生一个,则该类型可以是Monoid 。 在其中一个参数为中性值的情况下应用该函数会产生另一个参数。 String[Char] )是一个很好的示例,其中空字符串为中性值( mempty ),字符串连接为此类操作( mappend )。 请注意,可以使用两种不同的方法来构造数字的单素体:在中性值为0x + 0 == 0 + x == x对于任何x )的情况下以及与中性1乘法( x * 1 == 1 * x == x对于任何x )。 让我们坚持下去。 如果我折叠空字符串列表,我将得到一个空字符串。 但是,当列表包含许多元素时,它们将被串联起来:

> fold ([] :: [String])
""
> fold [] :: String
""
> fold ["foo", "bar"]
"foobar"

在第一个示例中,我们必须显式说明空列表[]的类型。 否则,Haskell编译器将无法弄清列表中元素的类型,因此无法选择哪个Monoid实例。 在第二个示例中,我们声明从fold []返回的任何内容都应该是String 。 据此,编译器推断[]实际上必须具有[String]类型。 最后fold是最简单的:程序会折叠列表中的元素并将其连接起来,因为连接是在Monoid String类型类实例中定义的操作。

返回选项(或更确切地说, Maybe )。 折叠具有类型参数为Monoid Maybe monad(我不能相信我刚刚说过)有一个有趣的解释:它要么返回Maybe内部的值,要么返回默认的Monoid值:

> fold (Just "abc")
"abc"
> fold Nothing :: String
""

Just "abc"与Scala中的Some("abc")相同。 您可以在此处看到,如果Maybe StringNothing ,则返回中性的String monoid值,即为空字符串。

摘要

Haskell表明折叠(也Maybe超过Maybe )至少可以保持一致。 在Scala中, Option.foldOption.fold无关,令人困惑List.fold可读。 我建议您避免使用它,而应该使用更详细的map / getOrElse转换或模式匹配。

PS:我是否提到过还有Either.fold() (甚至有不同的合同),但是没有Try.fold()吗?

翻译自: https://www.javacodegeeks.com/2014/06/option-fold-considered-unreadable.html

flink fold

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值