2012-03-14 16 views
10

Załóżmy, że chcę napisać klasę case Stepper następująco:klasa Case i linearyzacji cech

case class Stepper(step: Int) {def apply(x: Int) = x + step} 

Pochodzi z ładnym toString realizacji:

scala> Stepper(42).toString 
res0: String = Stepper(42) 

ale to naprawdę nie jest funkcją :

scala> Some(2) map Stepper(2) 
<console>:10: error: type mismatch; 
found : Stepper 
required: Int => ? 
       Some(2) map Stepper(2) 

Obejście polega na wdrożeniu Function cecha ...

case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step} 

Ale wtedy, nie mogę mieć za darmo ładnym realizacji toString już:

scala> Stepper(42).toString 
res2: java.lang.String = <function1> 

Potem, pytanie brzmi: Mogę mieć najlepsze z tych dwóch światów ? Czy istnieje rozwiązanie, w którym mam ładne wdrożenie toString za darmo i implementacji cechy Function. Innymi słowy, czy istnieje sposób na zastosowanie linearyzacji w taki sposób, że na koniec stosuje się cukier syntaktyczny case class?

Odpowiedz

8

Pytanie nie ma nic wspólnego z linearyzacją. W klasach przypadków toString jest metodą automatycznie generowaną przez kompilator wtedy i tylko wtedy, gdy Any.toString nie jest nadpisany w typie końcowym.

Jednak odpowiedź jest częściowo do czynienia z linearyzacji - musimy nadpisać Function1.toString metodą, która zostałaby generowanego przez kompilator jeśli nie dla wersji wprowadzonej Function1:

trait ProperName extends Product { 
    override lazy val toString = scala.runtime.ScalaRunTime._toString(this) 
} 

// now just mix in ProperName and... magic! 
case class Stepper(step: Int) extends (Int => Int) with ProperName { 
    def apply(x:Int) = x+step 
} 

Następnie

println(Some(2) map Stepper(2)) 
println(Stepper(2)) 

będzie produkować

Some(4) 
Stepper(2) 

Aktualizacja

Oto wersja ProperName cecha, która nie opiera się na metodzie nieudokumentowane API:

trait ProperName extends Product { 
    override lazy val toString = { 
    val caseFields = { 
     val arity = productArity 
     def fields(from: Int): List[Any] = 
     if (from == arity) List() 
     else productElement(from) :: fields(from + 1) 
     fields(0) 
    } 
    caseFields.mkString(productPrefix + "(", ",", ")") 
    } 
} 

alternatywna toString realizacja pochodzi z kodu źródłowego dla oryginalnego _toString metoda scala.runtime.ScalaRunTime._toString.

Należy pamiętać, że ta alternatywna implementacja nadal opiera się na założeniu, że klasa przypadków zawsze rozszerza się o Product. Chociaż ta ostatnia jest prawdziwa od wersji Scala 2.9.0 i jest faktem znanym i opartym na niektórych członkach społeczności Scala, nie jest ona formalnie dokumentowana jako część Scala Language Spec.

+0

Tak, to nie jest naprawdę linearyzacja, ale nie znalazłem żadnej innej odpowiedniej nazwy dla tego. I tego rodzaju sztuczki spodziewałem się, dzięki. – Nicolas

+0

@Nicolas Rozumiem Cię bardzo dobrze, często stwierdzam, że trudno jest opisać problem dokładnie wtedy, gdy nie jestem pewien, co się dzieje. –

+0

@ Vlad: Zawsze unikałem używania czegokolwiek z 'scala.runtime', które nie pojawia się w dokumentach API Scala. Zgadzam się, że jest to sprytne rozwiązanie, ale czy naprawdę uważasz, że warto, biorąc pod uwagę, że istnieją równie dobre rozwiązania, które wykorzystują proste, stare funkcje językowe Scala? –

2

EDYTOWANIE: Co z nadpisaniem toString?

case class Stepper(step: Int) extends (Int => Int) { 
    def apply(x: Int) = x + step 
    override def toString = "Stepper(" + step + ")" 
} 
+0

Tak, wiem o tym. ale naprawdę nie chcę dodawać, że nie ma zastosowania (wyobraź to sobie w DSL). – Nicolas

+0

Och, OK. A co powiesz o tym? –

+0

@ TalPressman zapomniałeś przedłużyć Funkcję 1 –

1

Można użyć niejawna konwersja mieć Stepper traktować jak funkcję tylko wtedy, gdy to konieczne:

case class Stepper(step: Int) { def apply(x: Int) = x + step } 

implicit def s2f(s: Stepper) = new Function[Int, Int] { 
    def apply(x: Int) = s.apply(x) 
} 

Teraz masz sprawa klasa na toString kiedy zadzwonić Stepper(42).toString, ale Some(2) map Stepper(2) działa również jako pożądane.

(Zauważ, że byłem bardziej szczegółowy niż to konieczne powyżej, aby utrzymać mechanikę w czystości. Możesz również napisać implicit def s2f(s: Stepper) = s.apply _ lub dowolną liczbę innych, bardziej zwięzłych sformułowań).

+0

Tak, ale budowanie tego niejawnego jest bardziej bolesne niż przepisywanie toString;) – Nicolas

+0

@Nicolas: Naprawdę? To 40 znaków. Wydaje się to znacznie mniej "bolesne" niż użycie nieudokumentowanego fragmentu API środowiska wykonawczego. –

+0

To nie jest kwestia liczby znaków, jest to kwestia dodania do klasy "niewysuszonego" kodu. Rozwiązanie 'ScalaRuntime' nie jest idealne, wyraźnie nie jest gotowe do produkcji, ale idzie w kierunku suchym. BTW, początkowo porównywałem to do przepisywania naString (patrz odpowiedź Tenta Pressmana) – Nicolas