【问题标题】:Should x._1,x._2... syntax be avoided?应该避免 x._1,x._2... 语法吗?
【发布时间】:2021-05-08 14:27:15
【问题描述】:

我刚开始接触 Scala。我发现自己经常使用元组变量。

例如,这是我写的一些代码:

/* Count each letter of a string and return in a list sorted by character
 * countLetter("test") = List(('e',1),('s',1),('t',2))
*/
def countLetters(s: String): List[(Char, Int)] = {
  val charsListMap = s.toList.groupBy((c:Char) => c)
  charsListMap.map(x => (x._1, x._2.length)).toList.sortBy(_._1)
}

Scala 开发人员是否不赞成这种元组语法(x._1、x._2 等)?

【问题讨论】:

  • 我认为这是 Scala 开发人员所希望的。 :)

标签: scala tuples


【解决方案1】:

Scala 开发人员是否不喜欢元组访问器?

简短回答:不。

略长(一个字符)答案:是的。

_n 太多可能是代码异味,在我看来,在您的情况下,以下内容更加清晰:

def countLetters(s: String): List[(Char, Int)] =
  s.groupBy(identity).mapValues(_.length).toList.sortBy(_._1)

有很多方法,如 mapValues,专门用于减少对嘈杂的元组访问器的需求,所以如果你发现自己写了很多 _1 等,那可能意味着你正在缺少一些不错的库方法。但有时它们是写东西的最干净的方式(例如,我重写的最后一个_1)。

要注意的另一件事是,过度使用元组访问器应被视为将元组提升为案例类的助推器。考虑以下几点:

val name = ("Travis", "Brown")

println("Hello, " + name._1)

相对于:

case class Name(first: String, last: String)

val name = Name("Travis", "Brown")

println("Hello, " + name.first)

第二个版本中额外的 case 类定义为单行代码购买了很多可读性。

【讨论】:

  • 这是一个很好的建议,但我认为mapValues 和朋友也有代码味道——更像是库的味道而不是用户空间的味道,但仍然有很多过于具体的方法需要被记住和维护。我宁愿在元组元素上有重新映射器:_.map(_.f2(_.length))
  • @RexKerr:我不知道——为mapValues 设置一个单独的方法清楚地表明_.map(_.f2(_.length)) 与地图上的_.map(_.f1(_.length)) 是一种非常不同的动物。我和语言设计师一起做这个。
  • 请在您的回答中提到mapValues 是懒惰的。它会减少内存使用量,但会在每次访问值时进行评估。
  • @senia:但是你总是需要在排序之前强制它(例如,这里使用toList)。我想我从来没有因为忘记mapValues的懒惰而被咬过。
  • @TravisBrown 呃,我有。非常讨厌;它发生在一些 GUI 代码中并导致内存泄漏。我至少花了一天时间才找到泄漏的原因。
【解决方案2】:

有一个比x._N 更好的解决方案。使用元组的常用方法是模式匹配:

charsListMap.map{case (a, b) => (a, b.length)}

您也可以看看scalaz,有一些instruments 用于元组:

import scalaz._
import Scalaz._

scala> (1, "a") bimap (_ + 2, _ + 2)
res0: (Int, java.lang.String) = (3,a2)

scala> ('s, "abc") :-> { _.length }
res1: (Symbol, Int) = ('s,3)

【讨论】:

  • @Jean-PhilippePellet:不是反对者,但这并不能回答真正的问题,而且mapValues 更习惯用语,无论如何。
  • @TravisBrown:您的答案中有 mapValues,这是 Map 的做法。但是模式匹配是更通用的解决方案。它几乎适用于元组的所有情况。
  • @TravisBrown 值得注意的是,mapValues 在原始地图周围创建了一个包装器,因此每次获得值时都会引入开销,而 senia 的解决方案会创建一个新的直接地图。
  • @senia:这是一个公平的观点,如果你的回答这么说,我会赞成的。
  • @Jean-PhilippePellet:我不确定这与这里是否相关(因为toList),特别是因为提问者是自称是初学者,并且在大多数情况下mapValues 确实正确的事。当然,如果你想编辑我的答案,我会祝福你。
【解决方案3】:

Scala 3 开始,使用parameter untupling 功能,以下将成为.map(x => x._1 -> x._2.length) 的替代方案:

.map(_ -> _.length)

因此,您的示例变为:

"test".toList.groupBy(identity).map(_ -> _.length).toList.sortBy(identity)
// List(("e", 1), ("s", 1), ("t", 2))

关于更具体的示例并从Scala 2.13 开始,您还可以使用groupMapReduce,它(顾名思义)相当于groupBy,后跟mapValues 和一个reduce 步骤:

"test".groupMapReduce(identity)(_ => 1)(_ + _).toList.sortBy(identity)

【讨论】:

    猜你喜欢
    • 2016-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-24
    • 2013-02-12
    • 2012-10-14
    • 2010-09-07
    相关资源
    最近更新 更多