【问题标题】:Scala Mutually Convertible Generic TypesScala 相互转换的泛型类型
【发布时间】:2015-10-16 16:19:01
【问题描述】:

我是 Scala 编程的新手,我非常喜欢代码的可组合程度。我想编写一些处理两个可相互转换的相关对象的特征,并通过继续扩展该特征来构建更多功能,以便在创建对象时可以为我的泛型指定相关类型。这是我正在谈论的代码类型的工作玩具示例:

trait FirstConverter[First] {
  def toFirst: First
}

trait SecondConverter[Second] {
  def toSecond: Second
}

trait TwoWayConverter[First <: SecondConverter[Second], Second <: FirstConverter[First]] {
  def firstToSecond(x: First) = x.toSecond
  def secondToFirst(x: Second) = x.toFirst
}

trait RoundTripConverter[First <: SecondConverter[Second], Second <: FirstConverter[First]] extends TwoWayConverter[First, Second] {
  def firstToFirst(x: First) = secondToFirst(firstToSecond(x))
  def secondToSecond(x: Second) = firstToSecond(secondToFirst(x))
}

case class A(s: String) extends SecondConverter[B] {
  def toSecond: B = B((s.toInt) + 1)
}

case class B(i: Int) extends FirstConverter[A] {
  def toFirst: A = A((i * 2).toString)
}

object ABConverter extends RoundTripConverter[A, B]

object Main {
  def main(args: Array[String]): Unit = {
    println(ABConverter firstToSecond A("10")) // 11
    println(ABConverter secondToFirst B(42)) // 84
    println(ABConverter firstToFirst A("1")) // 4
    println(ABConverter secondToSecond B(2)) // 5
  }
}

虽然这可行,但我不确定它是否是惯用的 Scala。我在问是否有任何技巧可以使类型定义更简洁,以及我是否可以以某种方式只定义一次类型限制并让它们被扩展其他特征的多个特征使用。

提前致谢!

【问题讨论】:

  • 也许只是文体上的小毛病:我不喜欢这段代码中 toInttoString 的后缀操作 - 我不认为它们在这里添加任何东西,对我来说它们只会损害可读性.特别是我更喜欢foo.s.toInt + 1 而不是(s toInt) + 1
  • @Suma - 是的,我还在学习什么时候用这个句号,什么时候不用。将它们添加回去。
  • @Wolfgang - 我曾经在同一个方向搜索过。看,我得到了什么:stackoverflow.com/questions/1154571/…

标签: scala generics types


【解决方案1】:

改进设计的一种方法是使用类型类,而不是从FirstConverterSecondConverter 继承。这样你就可以对相同的类型使用多个转换函数,并在你无法控制的类之间进行转换。

一种方法是创建一个可以将A 转换为B 的类型类:

trait Converter[A, B] {
  def convert(a: A): B
}

trait TwoWayConverter[A, B] {
  def firstToSecond(a: A)(implicit conv: Converter[A, B]): B = conv.convert(a)
  def secondToFirst(b: B)(implicit conv: Converter[B, A]): A = conv.convert(b)
}

trait RoundTripConverter[A, B] extends TwoWayConverter[A, B] {
  def firstToFirst(a: A)(implicit convAB: Converter[A, B], convBA: Converter[B, A]) =
    secondToFirst(firstToSecond(a))
  def secondToSecond(b: B)(implicit convAB: Converter[A, B], convBA: Converter[B, A]) =
    firstToSecond(secondToFirst(b))
}

我们可以为以下两个类 FooBar 创建类型类实例,类似于您的 AB

case class Foo(s: String)
case class Bar(i: Int)

implicit val convFooBarFoor = new Converter[Foo, Bar] {
  def convert(foo: Foo) = Bar((foo.s toInt) + 1)
}

implicit val convBarFoo = new Converter[Bar, Foo] {
  def convert(bar: Bar) = Foo((bar.i * 2) toString)
}

然后我们可以创建一个FooBarConverter

object FooBarConverter extends RoundTripConverter[Foo, Bar]

FooBarConverter firstToSecond Foo("10")  // Bar(11)
FooBarConverter secondToFirst Bar(42)    // Foo(84)
FooBarConverter firstToFirst Foo("1")    // Foo(4)
FooBarConverter secondToSecond Bar(2)    // Bar(5)

唯一的问题是因为我们不能将参数传递给特征,我们不能将类型限制为具有Converter 类型类实例的类型。因此,即使不存在 Converter[String, Int] 和/或 Convert[Int, String] 实例,您也可以在下面创建 StringIntConverter

object StringIntConverter extends TwoWayConverter[String, Int]

您不能调用StringIntConverter.firstToSecond("a"),因为firstToSecond 方法需要两个提到的类型类实例的隐式证据。

【讨论】:

  • "You cannot call StringIntConverter.firstToSecond("a")" - 幸运的是这仍然是检测到的编译时间,因此它似乎不是严重的限制。
  • 我更喜欢implicit object convBarFoo extends Converter[Bar, Foo] 而不是implicit val convBarFoo = new Converter[Bar, Foo] 以避免匿名上课,但您的口味可能会有所不同。
  • @Peter Neyens - 感谢新方法。我喜欢这如何保持类型“干净”,以及如何将我的功能添加到我无法控制的类型上。我想知道在extends RoundTripConverter 的对象内定义implicit Converters 的最佳位置在哪里?这是我可以编译您的代码的唯一方法,不幸的是,我似乎必须对任何使用它的函数使用implicit 参数,而不是在我的特征中使用implicit vals。有什么建议可以避免这种冗长吗?
猜你喜欢
  • 2020-06-16
  • 2013-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-04
  • 1970-01-01
  • 2011-09-12
相关资源
最近更新 更多