【问题标题】: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 方法参数类型,它是可以转换为给定类型的多种类型的集合?
【发布时间】:2016-08-11 16:48:37
【问题描述】:

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

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

trait 和 case 类如下所示:

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 并使用模式匹配,但这会检查参数是否运行时。

【问题讨论】:

    标签: scala collections types implicit-conversion


    【解决方案1】:

    这里的问题很少。

    您的隐式转换函数正在从Tuple2[Char, Int] 转换为Tuple2[Char, Int] =&gt; 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))
    

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

    附带说明,视图边界 (&lt;%) 为 deprecated。在这种情况下,您只需将&lt;% 替换为&lt;: 即可获得相同的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-11
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-05-26
      • 2018-06-12
      相关资源
      最近更新 更多