在 scala 中将元组直接解包到 class

Unpacking tuple directly into class in scala

Scala 提供了在执行各种操作时将元组解包为多个局部变量的能力,例如,如果我有一些数据

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK"))

那就不要做像

这样丑陋的事情
infos.map{ person_info => person_info._1 + " is " + person_info._2 }

我可以选择更优雅的

infos.map{ case (person, status) => person + " is " + status }

我经常想知道的一件事是如何将元组直接解包到,比如说,要在 class 构造函数中使用的参数。我在想象这样的事情:

case class PersonInfo(person: String, status: String)
infos.map{ case (p: PersonInfo) => p.person + " is " + p.status }

如果 PersonInfo 有方法则更好:

infos.map{ case (p: PersonInfo) => p.verboseStatus() }

但这当然行不通。抱歉,如果有人问过这个问题——我无法找到直接的答案——有没有办法做到这一点?

你的意思是这样的(scala 2.11.8):

scala> :paste
// Entering paste mode (ctrl-D to finish)

case class PersonInfo(p: String)

Seq(PersonInfo("foo")) map {
    case p@ PersonInfo(info) => s"info=$info / ${p.p}"
}

// Exiting paste mode, now interpreting.

defined class PersonInfo
res4: Seq[String] = List(info=foo / foo)

顺便说一下方法不行

对于 class

的情况,您可以使用 tuppled
val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK"))

infos.map(PersonInfo.tupled)

scala> infos: Array[(String, String)] = Array((Matt,Awesome), (Matt's Brother,Just OK))

scala> res1: Array[PersonInfo] = Array(PersonInfo(Matt,Awesome), PersonInfo(Matt's Brother,Just OK))

然后您可以根据需要使用PersonInfo

我相信您至少可以在 Scala 2 中了解这些方法。11.x,另外,如果您还没有听说过它,您应该查看 The Neophyte's Guide to Scala Part 1: Extractors

整个 16 部分系列非常精彩,但第 1 部分涉及案例 类、模式匹配和提取器,我认为您正在寻找这些。

此外,我在 IntelliJ 中也收到了 java.lang.String 投诉,由于我不完全清楚的原因,它默认为那个,我能够通过在典型中明确设置类型来解决它"postfix style" 即 _: String。不过一定有办法解决这个问题。

object Demo {

   case class Person(name: String, status: String) {
      def verboseStatus() = s"The status of $name is $status"
   }

   val peeps = Array(("Matt", "Alive"), ("Soraya", "Dead"))

   peeps.map {
     case p @ (_ :String, _ :String) => Person.tupled(p).verboseStatus()
   }

}

更新:

所以在看到其他一些答案后,我很好奇它们之间是否存在任何性能差异。所以我设置了一个我认为可能是使用 1,000,000 个随机字符串元组数组的合理测试,并且每个实现是 运行 100 次:

import scala.util.Random

object Demo extends App {

  //Utility Code
  def randomTuple(): (String, String) = {
    val random = new Random
    (random.nextString(5), random.nextString(5))
  }

  def timer[R](code: => R)(implicit runs: Int): Unit = {
    var total = 0L
    (1 to runs).foreach { i =>
      val t0 = System.currentTimeMillis()
      code
      val t1 = System.currentTimeMillis()
      total += (t1 - t0)
    }
    println(s"Time to perform code block ${total / runs}ms\n")
  }

  //Setup
  case class Person(name: String, status: String) {
    def verboseStatus() = s"$name is $status"
  }

  object PersonInfoU {
    def unapply(x: (String, String)) = Some(Person(x._1, x._2))
  }

  val infos = Array.fill[(String, String)](1000000)(randomTuple)

  //Timer
  implicit val runs: Int = 100

  println("Using two map operations")
  timer {
    infos.map(Person.tupled).map(_.verboseStatus)
  }

  println("Pattern matching and calling tupled")
  timer {
    infos.map {
      case p @ (_: String, _: String) => Person.tupled(p).verboseStatus()
    }
  }

  println("Another pattern matching without tupled")
  timer {
    infos.map {
      case (name, status) => Person(name, status).verboseStatus()
    }
  }

  println("Using unapply in a companion object that takes a tuple parameter")
  timer {
    infos.map { case PersonInfoU(p) => p.name + " is " + p.status }
  }
}

/*Results
  Using two map operations
  Time to perform code block 208ms

  Pattern matching and calling tupled
  Time to perform code block 130ms

  Another pattern matching without tupled
  Time to perform code block 130ms

  WINNER
  Using unapply in a companion object that takes a tuple parameter
  Time to perform code block 69ms
*/

假设我的测试是正确的,似乎 unapply 在一个伴随对象中比模式匹配快 ~2 倍,而模式匹配又比两个地图快 ~1.5 倍。每个实现可能都有其用途 cases/limitations。

如果有人看到我的测试策略中有任何明显愚蠢的地方让我知道,我将不胜感激(对于那个 var 感到抱歉)。谢谢!

案例 class 的提取器采用案例 class 的实例和 returns 其字段的元组。您可以编写一个执行相反操作的提取器:

object PersonInfoU {
  def unapply(x: (String, String)) = Some(PersonInfo(x._1, x._2))
}

infos.map { case PersonInfoU(p) => p.person + " is " + p.status }

可以合并几个答案以产生最终的统一方法:

val infos = Array(("Matt", "Awesome"), ("Matt's Brother", "Just OK"))

object Person{

  case class Info(name: String, status: String){
    def verboseStatus() = name + " is " + status
  }

  def unapply(x: (String, String)) = Some(Info(x._1, x._2))

}

infos.map{ case Person(p) => p.verboseStatus }

当然,在这个小案例中,它有点矫枉过正,但对于更复杂的用例,这是基本框架。