【问题标题】:Why is there no Tuple1 Literal for single element tuples in Scala?为什么 Scala 中没有用于单元素元组的 Tuple1 Literal?
【发布时间】:2011-06-23 21:09:28
【问题描述】:

Python 有 (1,) 用于单个元素元组。在 Scala 中,(1,2) 适用于 Tuple2(1,2),但我们必须使用 Tuple1(1) 来获取单个元素元组。这似乎是一个小问题,但对于传递单个元素的用户来说,设计期望产品的 API 是一件痛苦的事情,因为他们必须编写 Tuple1(1)。

也许这是一个小问题,但 Scala 的一个主要卖点是输入更多,输入更少。但在这种情况下,它似乎是更多的打字。

请告诉我: 1)我错过了这个,它以另一种形式存在,或者 2) 它将被添加到该语言的未来版本中(并且他们将接受补丁)。

【问题讨论】:

  • 我不明白 Tuple1 的目的是什么。
  • @paradigmatic 它允许一个值来实现Product 接口(例如,这样你就可以将它传递给一个以Product 作为参数的方法)。当然,这样会丢失静态类型。
  • @paradigmatic 有时第三方工具会实现需要任何元组作为输入的东西。例如,当使用breeze.linalg.DenseMatrix 创建数字矩阵时,其中一个构造函数采用元组的可变参数列表,其中每个元组对应于一行。如果要创建 Nx1 矩阵(单列,但键入为 Matrix 而不是向量),则可能需要将单元素元组的可变参数列表传递给该构造函数。关键是,即使人们无法预料为什么有人会需要它,但也太短视了,不能忽略它。

标签: scala syntax tuples


【解决方案1】:

你可以定义一个隐式转换:

implicit def value2tuple[T](x: T): Tuple1[T] = Tuple1(x)

仅当参数的静态类型不符合方法参数的类型时,才会应用隐式转换。假设您的方法采用 Product 参数

def m(v: Product) = // ...

转换将应用于非产品值,但不会应用于Tuple2。警告:所有 case 类都扩展了 Product 特征,因此转换也不适用于它们。相反,产品元素将是案例类的构造函数参数。

ProductTupleX 类的最小上限,但如果您想将隐式 Tuple1 转换应用于所有非元组,则可以使用类型类:

// given a Tupleable[T], you can call apply to convert T to a Product
sealed abstract class Tupleable[T] extends (T => Product)
sealed class ValueTupler[T] extends Tupleable[T] { 
   def apply(x: T) = Tuple1(x) 
}
sealed class TupleTupler[T <: Product] extends Tupleable[T] { 
   def apply(x: T) = x 
}

// implicit conversions
trait LowPriorityTuple {
   // this provides a Tupleable[T] for any type T, but is the 
   // lowest priority conversion
   implicit def anyIsTupleable[T]: Tupleable[T] = new ValueTupler
}
object Tupleable extends LowPriorityTuple {
   implicit def tuple2isTuple[T1, T2]: Tupleable[Tuple2[T1,T2]] = new TupleTupler
   implicit def tuple3isTuple[T1, T2, T3]: Tupleable[Tuple3[T1,T2,T3]] = new TupleTupler
   // ... etc ...
}

您可以按如下方式在 API 中使用此类型类:

def m[T: Tupleable](v: T) = { 
   val p = implicitly[Tupleable[T]](v) 
   // ... do something with p
}

如果您的方法返回产品,您可以看到转换是如何应用的:

scala> def m[T: Tupleable](v: T) = implicitly[Tupleable[T]](v)
m: [T](v: T)(implicit evidence$1: Tupleable[T])Product

scala> m("asdf") // as Tuple1
res12: Product = (asdf,)

scala> m(Person("a", "n")) // also as Tuple1, *not* as (String, String)
res13: Product = (Person(a,n),)

scala> m((1,2)) // as Tuple2
res14: Product = (1,2)

【讨论】:

  • 这种方法似乎很完整,但我想知道为什么不只使用隐式参数,例如 toTuple take (implicit m: TupleConverter[T]) 它的隐式值与您所做的类似。这似乎是更少的代码行。对此方法有何评论?
  • 我不确定我是否遵循您的建议,但听起来您的意思是您的 API 方法仍将采用 Product 参数。如果是这样,案例类将包装在 Tuple1 中,因为它们已经扩展了 Product 特征(因此不需要转换)。
  • @Oscar Boykin This answer 可能会提供一种更简单的方法。也许这就是您在评论时的想法?
  • 谢谢,亚伦。这个例子几乎是完全相关的。
【解决方案2】:

当然,您可以在 API 中添加隐式转换:

implicit def value2tuple[A](x: A) = Tuple1(x)

我确实觉得 Tuple1.toString 包含尾随逗号很奇怪:

scala> Tuple1(1)
res0: (Int,) = (1,)

【讨论】:

  • 该死,你秒杀我。 :)
  • 如果您正在制作一个想要接受单个项目或项目元组的 DSL,该怎么办。如果我做 value2tuple, a tuple2 -> Tuple1[Tuple2[A,B]],这不是我想要的。我想要非元组 -> Tuple1[A] 和 TupleX -> TupleX。我可以对 value2tuple 中的类型进行某种限制吗?
【解决方案3】:

Python 不是静态类型的,所以那里的元组更像是固定大小的集合。 Scala 并非如此,其中元组的每个元素都有不同的类型。 Scala 中的元组与 Python 中的用途不同。

【讨论】:

  • 静态类型对我来说似乎无关紧要。当您在 scala 中打印 Tuple1 时,它会打印为 (1,)。您不能将其用作文字似乎不一致。
  • @Oscar Scala 类型的 mkString 不是有效的文字。例如,List("abc") 打印为 List(abc)。相反,它们以可读性为目标。在这种情况下,额外的逗号只是表示它代表一个元组。顺便说一句,尾随的逗号 在几个版本之前都是有效的,并且被删除了,因为它增加了太多的复杂性而获得的收益太少。
  • @Oscar 而静态类型的要点是 Tuple1 在 Scala 中没有使用。在 Python 中,它用作大小为 1 的固定宽度集合,但 Scala 中的元组是 not 集合,因此没有用处。
  • 元组是 Product 实例,有点像集合,所以我不确定我是否同意这个论点。
  • @Oscar 所有产品提供的是一个迭代器。迭代器不是集合,没有人将元组用于其Product
猜你喜欢
  • 2019-01-14
  • 2012-11-22
  • 2018-02-13
  • 2019-08-11
  • 1970-01-01
  • 1970-01-01
  • 2020-04-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多