2015-10-16 72 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