记录Scala功能链

时间:2011-05-26 10:58:17

标签: scala documentation functional-programming

Scala(以及函数式编程,一般而言),提倡一种编程风格,您可以在其中生成表单的功能“链”

collection.operation1(...)。操作2(...)...

其中操作是mapfilter等的各种组合

如果等效的Java代码可能需要50行,则Scala代码可以在1行或2行中完成。功能链可以将输入集合更改为完全不同的东西。

Scala代码的缺点是10分钟后(从不介意6个月后),我无法弄清楚我在想什么,因为符号是如此紧凑,缺少类型信息(因为隐含的类型)

你如何记录这个?您是否在链条之前放置了一个大块注释,将优雅的1线解决方案转换为由39行评论组成的庞大的40行解决方案?你是否像这样穿插你的评论?

collection.
  // Select the items that meet condition X
  filter(predicate_function).
  // Change these items from A's to B's
  map(transformation_function).
  // etc.

还有别的吗?没有文件? (让他们猜测。他们永远不会“缩小”你,因为没有其他人可以维护代码。: - ))

6 个答案:

答案 0 :(得分:10)

如果您发现自己在详细程度上撰写评论,那么您只需重复代码所说的内容。

对于长功能链,定义新功能以替换链的部分。给这些有意义的名字。然后你可以避免评论。这些函数的名称本身应该解释它们的作用。

最好的评论是那些解释为什么代码执行某些操作的评论。编写良好的代码应该使代码本身的明显“如何”

答案 1 :(得分:8)

我不会开始编写该代码(除非它是一次性使用的脚本或在REPL中播放)。

如果我可以在一条评论中解释代码的作用并且读取正常,那么我将其保留为一行:

// Find all real-valued square roots and group them in integer bins
ds.filter(_ >= 0).map(math.sqrt).groupBy(_.toInt).map(_._2)

如果我不能通过仔细阅读命令链来理解这一点,那么我应该将其分解为功能不同的单元。例如,如果我期望某人没有意识到负数的平方根不是实数,我会说:

// Only non-negative numbers have a real-valued square root
val nonneg = ds.filter(_ >= 0)
// Find square roots and group them in integer bins
nonneg.map(math.sqrt).groupBy(_.toInt).map(_._2)

特别是,如果有人不熟悉Scala集合库,并且没有耐心花5到10分钟理解一行代码,那么他们不应该处理我的代码(也不是在其他任何完成一些他们不理解并且没有耐心去理解的事情的事情上,或者我应该提前知道我提供了一个例如语言和数学教程,除了编写工作代码外,还可以编写一段解释后续行如何工作的段落,或者逐个命令地删除它,或者在每个匿名函数的开头包含注释来解释正在发生的事情(视情况而定)

无论如何,如果你无法理解它的作用,你可能需要一些中间值。他们对精神重置非常有帮助(“我看不出如何从A到C!......但是......好吧,我能理解A到B.我能理解B到C。”)< / p>

答案 2 :(得分:6)

如果你的链式操作都是monadic变换:mapflatMapfilter,那么将逻辑重写为for-comprehension通常要清楚得多。

coll.filter(predicate).map(transform)

可能会成为

for(elem <- coll if predicate) yield transform(elem)
如果您有更长的操作序列,那么展示技术的力量就更容易了,例如Kassen的例子:

def eligibleCustomers(products: Seq[Product]) = for {
  product <- products
  customer <- product.customers
  paying <- customer if customer.isPremium
  eligible <- paying if paying.age < 20
} yield eligible

答案 3 :(得分:2)

如果您不希望在多个方法中将其拆分为hammar建议您可以拆分该行并提供中间值名称(以及可选的类型)。

def eligibleCustomers: List[Customer] = {
  val customers = products.flatMap(_.customers)
  val paying = customers.filter(_.isPremium)
  val eligible = paying.filter(_.age < 20)
  eligible
}

答案 4 :(得分:2)

当您的连锁店变得太长时,线长是某种自然的指标。 :)

当然,这将取决于链条的微不足道:

customerdata.filter (_.age < 40).filter (_.city == "Rio").
             filter (_.income > 3000).filter (_.joined < 2005) 
             filter (_.sex == 'f'). ...

我最近有过你的印象,其中3个文件的应用,其中一个有点冗长,由4个类组成,其中一个不是微不足道的,大约有10到20个方法。每种方法大约有5到10行,并且每个方法可以很容易地组合成一个较大的方法,但我不得不说服自己,虽然测量代码行的优雅并不是完全错误,但是节省的行不是目标本身。

但是将方法拆分为两个通常会使每行的复杂度降低,而不是整体复杂性,以便理解整个程序。

如果问题域很复杂 - 过滤不同级别的数据,按行,按列,映射,分组,构建平均值,构建图形,对它们进行分页...... - 复杂的工作必须在某处完成。

该程序不易理解,您只需不经常按 page down 。这是一个重新调整,你必须更慢地阅读一行代码。

答案 5 :(得分:0)

现在我已经习惯了Scala,这并不会让我感到烦恼。如果您希望更明确地使用类型,则可以始终将map(_.foo)之类的内容替换为map { a:A => a.foo },以使代码在冗长/复杂的操作中更具可读性。并非我通常认为有必要这样做。