2015-10-16 95 views
0

我对Scala编程非常陌生,我非常喜欢代码可组合的程度。我想写一些处理可相互转换的两个相关对象的特征,并通过继续扩展该特征来构建更多功能,以便在创建对象时可以指定我的泛型的相关类型。这里是我正在谈论的代码类型的工作玩具示例: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。我问是否有任何技巧可以使类型定义更加简洁,如果我可以以某种方式定义类型限制只有一次,并让它们用于扩展其他特征的多种特征。

在此先感谢!

+0

也许只是一种文体上的宠物:我不喜欢这段代码中toInt或toString的postfix操作 - 我不认为它们在这里添加任何东西,对我来说它们只会损害可读性。特别是我更喜欢'foo.s.toInt + 1' over'(s toInt)+ 1'。 – Suma

+0

@Suma - 真的,我仍然在学习什么时候使用这个时间,什么时候不使用。添加了那些。 – Wolfgang

+0

@Wolfgang - 我在同一个方向上搜索了一次。看到,我得到了:http://stackoverflow.com/questions/1154571/scala-abstract-types-vs-generics/10891994#10891994 – ayvango

回答

3

改进设计的一种方法是使用类型类而不是从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)) 
} 

我们可以为以下两类Foo创建类型类实例和Bar类似于您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方法需要提到的两个类型的类实例的隐含证据。

+0

“你不能调用'StringIntConverter.firstToSecond(”a“)'' - 幸运的是,仍然可以检测到编译时间,因此它看起来不像是严重的限制。 – Suma

+0

我宁愿'隐式对象convBarFoo扩展转换器[酒吧,富]'隐式val convBarFoo =新转换器[酒吧,富]'以避免匿名类,但你的口味可能会有所不同。 – Suma

+0

@Peter Neyens - 感谢新方法。我喜欢这样保持类型“干净”,以及如何将我的功能添加到我无法控制的类型上。我想知道在'扩展RoundTripConverter'的对象内部定义隐式转换器的最佳位置是什么?这是我可以让你的代码编译的唯一方法,不幸的是,我似乎不得不使用隐式参数给任何使用它的函数,而不是在我的特性中使用隐式val。任何建议如何避免这种冗长? – Wolfgang