如何制作一个 scala 方法参数类型,它是可以转换为给定类型的多种类型的集合?

How can I make a scala method parameter type that is a collection of multiple types that can be converted to a given type?

我想创建一个 scala 方法参数类型,它是多种类型的集合,这些类型都可以转换为通用的给定类型,但我无法使用隐式转换来实现,类型 class es、上下文边界和视图边界。

我已经创建了一个特征并且 class class 扩展了该特征,我想编写一个方法允许其他开发人员传入我的代码将处理的实例集合。但是,我不希望我的方法的调用者创建一个实例集合。相反,我希望调用者传入一个元组集合,其中元组必须来自定义的一组元组。

特征和案例 class 看起来像这样:

sealed trait Widget {
  type W
  def identifier: W
  def number: Int
}

case class CWidget(identifier: Char, number: Int) extends Widget {type W = Char}
case class SWidget(identifier: String, number: Int) extends Widget {type W = String}
case class CSWidget(identifier: (Char, String), number: Int) extends Widget {type W = (Char, String)}

方法如下所示:

def getWidgets[W <% Widget](s: Seq[W]): Unit = ...

目前,允许的元组具有以下类型:(Char, Int)(String, Int)(Char, String, Int)。最终,我希望能够允许像 (Int, Char) 这样混淆顺序的元组,但那是另一回事了。

我创建了这些隐式方法并使用 import:

将它们引入范围
implicit def fromTuple(t: (Char, Int)) = ((c: Char, n: Int) => CWidget(c, n)) tupled t
implicit def fromTuple(t: (String, Int)) = ((s: String, n:Int) => SWidget(s, n)) tupled t
implicit def fromTuple(t: (Char, String, Int)) = ((c: Char, s: String, n: Int) => CSWidget((c, s), n)) tupled t

我从 REPL 试过这个:

getWidgets(Seq(('a',1),("string",2),('c',"string",3)))

并收到此错误:

<console>:26: error: No implicit view available from Product with Serializable => Widget.

提前致谢!

已编辑:

这是实施答案的结果(谢谢,@gregghz!):

sealed trait Widget {
  type W
  def identifier: W
  def number: Int
}

case class CWidget(identifier: Char, number: Int) extends Widget {type W = Char}
case class SWidget(identifier: String, number: Int) extends Widget {type W = String}
case class CSWidget(identifier: (Char, String), number: Int) extends Widget {type W = (Char, String)}

implicit def fromTupleCI(t: (Char, Int)): Widget = {
  val (c, n) = t
  CWidget(c, n)
}

implicit def fromTupleSI(t: (String, Int)): Widget = {
  val (s, n) = t
  SWidget(s, n)
}

implicit def fromTupleCSI(t: (Char, String, Int)): Widget = {
  val (c, s, n) = t
  CSWidget((c, s), n)
}

def getWidgets(s: Widget*): Unit = {
  s.foreach { w => println(w) }
}

getWidgets(('a',1), ('b',2), ('c', "abc", 3), ("abcd", 4)

结果如下:

CWidget(a,1)
CWidget(b,2)
CSWidget((c,abc),3)
SWidget(abcd,4)

这很好,因为我不想强制调用者为 getWidgets 方法创建一个 Seq。

最重要的是,它会在编译时检查参数的类型(感谢静态类型!)。我本可以将 getWidgets 参数设置为 Any 并使用模式匹配,但这会检查参数是否为 运行-time.

这里问题不大。

您的隐式转换函数正在从 Tuple2[Char, Int] 转换为 Tuple2[Char, Int] => Widget。您希望它们直接转换为小部件。

它们看起来应该更像这样:

implicit def fromTuple(t: (Char, Int)): Widget = {
  val (c, n) = t
  CWidget(c, n)
}
implicit def fromTuple(t: (String, Int)): Widget = {
  val (s, n) = t
  SWidget(s, n)
}
implicit def fromTuple(t: (Char, String, Int)): Widget = {
  val (c, s, n) = t
  CSWidget((c, s), n)
}

但是,这不会按原样工作。 def fromTuple(t: (Char, Int))def fromTuple(t: (String, Int)) 在发生类型擦除后是相同的类型。这意味着运行时无法区分这两个函数,也不知道调用哪一个。事实证明这很容易修复。只需将函数的名称更改为唯一即可:

implicit def fromTupleCI(t: (Char, Int)) = ...
implicit def fromTupleSI(t: (String, Int)) = ...
implicit def fromTupleCSI(t: (Char, String, Int) = ...

接下来,由于您对 getWidgets 的调用采用 Seq,因此首先推断出 Seq 的类型。由于各种元组具有 Product 的共同父级,因此您的 Seq 可以粗略地推断为 Seq[Product]。没有从 Seq[Product]Seq[Widget] 的隐式转换。有两种解决方法。

你可以显式指定类型参数给Seq:

getWidgets(Seq[Widget](('a',1),("string",2),('c',"string",3)))

这将触发元组到 Widgets

的隐式转换

您还可以更改 getWidgets 的签名以获取小部件的可变参数。

def getWidgets(s: Widget*): Unit = ...

getWidgets(('a',1), ("string",2), ('c',"string",3))

同样,这会在您希望它发生的地方触发隐式转换。

附带说明一下,视图范围 (<%) 是 deprecated。在这种情况下,您可以简单地将 <% 替换为 <: 并获得相同的结果。