Scala教程–代码块,编码样式,闭包,scala文档项目

前言

这是面向初学者的Scala教程的第12部分。 该博客上还有其他文章,您可以在我正在为其创建的计算语言学课程的链接页面上找到这些链接和其他资源。 另外,您可以在“ JCG Java 教程”页面上找到本教程和其他教程系列。

这篇文章并不仅仅是教程,而是关于编码风格的评论,并提供了一些有关Scala中代码块如何工作的指针。 我在学生代码中注意到的模式引起了这种情况。 也就是说,他们将所有内容打包到一个地图中,一个地图又一个地图,另一个地图又一个地图,等等。对于其他阅读代码的人来说,这些map-over-mapValues-over-map语句序列几乎是难以理解的。即使是写代码的人。 我确实承认在课堂授课中甚至在其中一些教程中使用这样的操作序列会产生很大的内感。 在REPL中,当您有大量文本来解释所涉及的代码所发生的事情时,它可以很好地工作,但是似乎为编写实际代码提供了一个不好的模型。 糟糕!

因此,退后一步,将操作顺序打乱很重要,但是对于初学者而言,如何做到这一点并不总是显而易见的。 另外,一些学生表示,他们已经获得了一种印象,即如果可能的话,应该尝试将所有内容打包到一条线上,并且分解这些内容在某种程度上不那么先进或不像Scala那样。 事实并非如此。 实际上恰恰相反:使用使您的代码阅读者看到您的语句背后的逻辑的策略至关重要。 这不仅仅适用于其他人,您可能是您自己的代码的阅读者,通常是在最初编写它的几个月之后,并且您希望对自己的未来变得友善。

一个简单的例子

我在这里举一个例子。 如何为代码提​​供更多的喘息空间。 这不是一个非常有意义的例子,但是它可以达到目的,而不会非常复杂。 我们首先创建字母表中所有字母的列表。

scala> val letters = "abcdefghijklmnopqrstuvwxyz".split("").toList.tail
letters: List[java.lang.String] = List(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)

好的,这是我们的(毫无意义的)任务:我们想创建一个从每个字母(从'a'到'x')到包含该字母以及其后两个字母的字母反序列表。 (我是否提到这本身就是毫无意义的任务?)这是一个可以做到的单线工作。

scala> letters.zip((1 to 26).toList.sliding(3).toList).toMap.mapValues(_.map(x => letters(x-1)).sorted.reverse)
res0: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(e -> List(g, f, e), s -> List(u, t, s), x -> List(z, y, x), n -> List(p, o, n), j -> List(l, k, j), t -> List(v, u, t), u -> List(w, v, u), f -> List(h, g, f), a -> List(c, b, a), m -> List(o, n, m), i -> List(k, j, i), v -> List(x, w, v), q -> List(s, r, q), b -> List(d, c, b), g -> List(i, h, g), l -> List(n, m, l), p -> List(r, q, p), c -> List(e, d, c), h -> List(j, i, h), r -> List(t, s, r), w -> List(y, x, w), k -> List(m, l, k), o -> List(q, p, o), d -> List(f, e, d))

做到了,但是那种单线根本不是很清楚,所以我们应该将其分解。 另外,“ _”是什么,“ x”是什么? (我的意思是,它们在程序逻辑方面是什么?我们知道它们是引用被映射的元素的方式,但它们并不能帮助阅读代码的人员理解正在发生的事情。)

让我们从创建数字范围的滑动列表开始。

scala> val ranges = (1 to 26).toList.sliding(3).toList
ranges: List[List[Int]] = List(List(1, 2, 3), List(2, 3, 4), List(3, 4, 5), List(4, 5, 6), List(5, 6, 7), List(6, 7, 8), List(7, 8, 9), List(8, 9, 10), List(9, 10, 11), List(10, 11, 12), List(11, 12, 13), List(12, 13, 14), List(13, 14, 15), List(14, 15, 16), List(15, 16, 17), List(16, 17, 18), List(17, 18, 19), List(18, 19, 20), List(19, 20, 21), List(20, 21, 22), List(21, 22, 23), List(22, 23, 24), List(23, 24, 25), List(24, 25, 26))

现在已经很清楚了。 ( 滑动功能很漂亮,尤其是对于自然语言处理问题。)

接下来,我们将字母与范围压缩在一起,并使用toMap从这些对中创建一个Map 。 这将产生一个从字母到三个数字的列表的映射。 请注意,两个列表的长度不同: 字母包含26个元素, 范围包含24个元素,这意味着字母的最后两个元素(“ y”和“ z”)将被放入压缩列表中。

scala> val letter2range = letters.zip(ranges).toMap
letter2range: scala.collection.immutable.Map[java.lang.String,List[Int]] = Map(e -> List(5, 6, 7), s -> List(19, 20, 21), x -> List(24, 25, 26), n -> List(14, 15, 16), j -> List(10, 11, 12), t -> List(20, 21, 22), u -> List(21, 22, 23), f -> List(6, 7, 8), a -> List(1, 2, 3), m -> List(13, 14, 15), i -> List(9, 10, 11), v -> List(22, 23, 24), q -> List(17, 18, 19), b -> List(2, 3, 4), g -> List(7, 8, 9), l -> List(12, 13, 14), p -> List(16, 17, 18), c -> List(3, 4, 5), h -> List(8, 9, 10), r -> List(18, 19, 20), w -> List(23, 24, 25), k -> List(11, 12, 13), o -> List(15, 16, 17), d -> List(4, 5, 6))

请注意,我们可以将其分为两个步骤,首先创建压缩列表,然后在其上调用toMap 。 但是,非常清楚的是,当一个人拉近两个列表(创建一个对的列表)然后立即在其上使用toMap时 ,其意图是什么,因此在将多个操作放在一行上是很有意义的。

在这一点上,我们当然可以使用单线处理letter2range Map

scala> letter2range.mapValues(_.map(x => letters(x-1)).sorted.reverse)
res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(e -> List(g, f, e), s -> List(u, t, s), x -> List(z, y, x), n -> List(p, o, n), j -> List(l, k, j), t -> List(v, u, t), u -> List(w, v, u), f -> List(h, g, f), a -> List(c, b, a), m -> List(o, n, m), i -> List(k, j, i), v -> List(x, w, v), q -> List(s, r, q), b -> List(d, c, b), g -> List(i, h, g), l -> List(n, m, l), p -> List(r, q, p), c -> List(e, d, c), h -> List(j, i, h), r -> List(t, s, r), w -> List(y, x, w), k -> List(m, l, k), o -> List(q, p, o), d -> List(f, e, d))

这比我们刚开始时要好,因为我们至少知道letter2range是什么,但是仍然不清楚之后发生了什么。 为了使它更容易理解,我们可以将其分解成多行,并为变量赋予更多描述性名称。 以下产生与上面相同的结果。

letter2range.mapValues (
  range => {
    val alphavalues = range.map (number => letters(number-1))
    alphavalues.sorted.reverse
  }
)

注意:

  • 我称它为range而不是_ ,它更好地指示了mapValues正在使用的对象。
  • 在=>之后,我使用左方括号{
  • 接下来的几行代码是我可以像使用任何代码块一样使用的代码块,这意味着我可以创建变量并将其分解为更小的,更易于理解的步骤。 例如,创建alphavalues的行清楚地表明,我们正在获取一个范围并将其映射到字母列表中的相应索引(例如,范围2、3、4变为'b','c','d')。 对于这样的列表,我们然后对其进行排序和反转(好的,所以它开始是排序的,但是您可以想象需要很多次进行这种排序)。
  • 该块的最后一行是该元素的总体mapValue结果(在此由变量range指示)。

基本上,我们有更多的呼吸空间,而当您在地图内地图操作中进行更深入的挖掘或进行更复杂的操作时,这变得尤为重要。 话虽如此,您应该问自己是否应该创建和使用一个具有清晰语义并为您完成工作的函数。 例如,这是上述策略的替代方法,可能更清楚了。

def lookupSortAndReverse (range: List[Int], alpha: List[String]) =
  range.map(number => alpha(number-1).sorted.reverse)

我们定义了一个函数,该函数接受一个范围和一个字母列表(在函数中称为alpha),并生成与该范围中的数字相对应的字母排序和反向列表。 换句话说,这就是在上一个代码块中range之后定义的匿名函数所做的事情。 因此,我们可以在顶层mapValue操作中轻松使用它,并具有完全清晰的意图和可理解性。

letter2range.mapValues(range => lookupSortAndReverse(range, letters))

当然,如果您在多个地方使用相同的操作,则应特别考虑创建此类函数。

关闭

进一步的最后说明。 请注意,我将字母列表传递给lookupSortAndReverse函数,以便其值绑定到函数内部变量alpha 。 您可能想知道我是否需要包含它,或者是否可以直接访问函数中的字母列表。 实际上,您可以: 只要已经定义了字母 ,我们就可以执行以下操作。

def lookupSortAndReverseCapture (range: List[Int]) =
  range.map(number => letters(number-1).sorted.reverse)

letter2range.mapValues(range => lookupSortAndReverseCapture(range))

这称为闭包 ,这意味着该函数已合并了来自其自身范围之外的自由变量(此处为字母 )。 我通常不将这种策略与这样的命名函数一起使用,但是使用闭包有很多自然的情况。 实际上,在创建匿名函数作为mapmapValue及其表亲之类的函数的参数时,您始终会这样做 。 提醒一下,这是我们之前定义的map-in-a-mapValue匿名函数。

letter2range.mapValues (
  range => {
    val alphavalues = range.map (number => letters(number-1))
    alphavalues.sorted.reverse
  }
)

字母变量已在匿名函数范围=> {…}中 “关闭”,这与我们对闭包式lookupSortAndReverse函数所做的操作没有太大不同。

所有代码合而为一

由于本教程中不同步骤之间存在某些依赖关系,可能使事情复杂化,因此这里的所有代码都可以一目了然,因此您可以轻松地运行它。

// Get a list of the letters
val letters = "abcdefghijklmnopqrstuvwxyz".split("").toList.tail

// Now create a list that maps each letter to a list containing itself
// and the two letters after it, in reverse alphabetical
// order. (Bizarre, but hey, it's a simple example. BTW, we lose y and
// z in the process.)

letters.zip((1 to 26).toList.sliding(3).toList).toMap.mapValues(_.map(x => letters(x-1)).sorted.reverse)

// Pretty unintelligible. Let's break things up a bit

val ranges = (1 to 26).toList.sliding(3).toList
val letter2range = letters.zip(ranges).toMap
letter2range.mapValues(_.map(x => letters(x-1)).sorted.reverse)

// Okay, that's better. But it is easier to interpret the latter if we break things up a bit

letter2range.mapValues (
  range => {
    val alphavalues = range.map (number => letters(number-1))
    alphavalues.sorted.reverse
  }
)

// We can also do the one-liner coherently if we have a helper function.

def lookupSortAndReverse (range: List[Int], alpha: List[String]) =
  range.map(number => alpha(number-1).sorted.reverse)

letter2range.mapValues(range => lookupSortAndReverse(range, letters))

// Note that we can "capture" the letters value, though this makes the
// requires letters to be defined before lookupSortAndReverse in the
// program.

def lookupSortAndReverseCapture (range: List[Int]) =
  range.map(number => letters(number-1).sorted.reverse)

letter2range.mapValues(range => lookupSortAndReverseCapture(range))

包起来

希望这会鼓励您使用更清晰的编码样式,并演示一些您可能尚未意识到的代码块。 但是,这只是编写更清晰的代码的基础,并且很多时间和实践都会伴随着时间的流逝,并且当您回顾几个月前编写的代码时,意识到实现它的必要性。

请注意,创建更好的代码可以做的一件简单的事情就是尝试遵循已建立的编码约定。 例如,请参见Scala文档项目上的Scala 编码准则 。 还有很多其他非常有用的内容,包括教程,并且它也在不断发展和壮大!

参考: 对于初学者来说,Scala的第一步,来自Bcompose博客的JCG合作伙伴 Jason Baldridge的 第12部分

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/11/scala-tutorial-code-blocks-coding-style.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值