2013-06-27 25 views
6

Pracuję nad małą biblioteką dla modeli ekonomicznych, które sprawdzają jednostki jednostek, używając typów, np. zamiast val apples = 2.0 piszemy val apples = GoodsAmount[KG, Apples](2.0). Do tworzenia pakietu towarów, próbuję użyć HListów z bezkształtnej biblioteki. Działa to dobrze, ale w niektórych przypadkach nie mogę być tak ogólny, jak wolę. Zobacz np. następujący problem.Bezkształtne: sprawdzanie ograniczeń typów polimorficznych

Zaczynam od prostego kodu, który wyjaśnia, co chcę unieść w bezkształtne. Tworzymy dwie klasy, na których reprezentują Km, inne Mile. Powinno być dozwolone dodawanie klas Km, ale nie mil. To, że używam abstrakcyjnego typu T, jest motywowane głównie naszą bardziej złożoną biblioteką. I pośrednie wywołanie funkcji "+" jest po prostu dlatego, że potrzebujemy czegoś podobnego w bezkształtnej sprawie.

trait Foo { 
    type T 
    val v: Double 
    def +[B <: Foo](other: B)(implicit ev: this.T =:= other.T) = v + other.v 
} 

trait _Km 
trait _Miles 

case class Km(v: Double) extends Foo { type T = _Km } 
case class Miles(v: Double) extends Foo { type T = _Miles } 

object ExampleSimple extends App { 
    def add[A <: Foo, B <: Foo](a: A, b: B)(implicit ev: a.T =:= b.T) = { a + b } 

    add(Km(1), Km(2)) 
    // add(Km(1), Miles(2)) /* does not compile as intended */ 
} 

Działa to zgodnie z przeznaczeniem. Konieczne jest jednak sprawdzenie typu w funkcji "dodaj". Moja próba rozszerzyć to HLists wygląda następująco:

object ExampleShapeless extends App { 
    import shapeless._ 

    val l1 = Km(1) :: Km(2) :: HNil 
    val l2 = Km(4) :: Km(3) :: HNil 

    object add extends Poly1 { 
    implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a, b) => a + b } 
    } 

    (l1 zip l2).map(add) 
} 

Ale to generuje następujący komunikat o błędzie (używając Scala 2.10.2):

[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:50: Cannot prove that a.T =:= b.T. 
[error]  implicit def caseTuple[A <: Foo] = at[(A,A)] { case (a: Foo, b) => a + b } 
[error]                  ^
[error] /home/fuerst/gitg3m/code/types/src/main/scala/lagom_d/extract.scala:54: could not find implicit value for parameter mapper: shapeless.Mapper[ExampleShapeless.add.type,shapeless.::[(Km, Km),shapeless.::[(Km, Km),shapeless.HNil]]] 
[error] (l1 zip l2).map(add) 

Pierwszy błąd powinien zostać ustalony w przypadku że mogę dodać wiązanie typu do funkcji caseTuple, ale szczerze mówiąc, nie zrozumiałem, jak działa funkcja at i gdzie mogę dodać parametr implicite evidence. I nie wiem też, co muszę zrobić, żeby Mapper znalazł swoją ukrytą wartość.

Mniej generycznej wersji, gdzie replase funkcję caseTuple z

implicit def caseTuple = at[(Km,Km)] { case (a, b) => a + b } 

działa dobrze, ale trzeba pisać dużo nadmiarowego kodu (okay, rozwiązanie to byłoby jeszcze lepiej, jak nasze obecne rozwiązanie wykorzystujące Krotki). Czy ktoś może mi podpowiedzieć, jak mogę rozwiązać ten problem?

Dzięki, Klinke

+0

Możesz spróbować zdefiniować swoje 'Foo' w ten sposób:' cecha Foo [T <: Foo] {v: Double; + (t T): T = ...} '. 'class Km (val v: Double) rozszerza Foo [Km]'. 'niejawny def dodaj [T] = w [(Foo [T], Foo [T])]' – senia

Odpowiedz

7

Można wymagać członków typu dopasować dodając parametr typu do postępowania:

object add extends Poly1 { 
    implicit def caseTuple[_T, A <: Foo { type T = _T }] = at[(A, A)] { 
    case (a, b) => a + b 
    } 
} 

Albo można użyć typu egzystencjalnego, skoro tylko troszczą się, że są takie same:

object add extends Poly1 { 
    implicit def caseTuple[A <: Foo { type T = _T } forSome { type _T }] = 
    at[(A, A)] { 
     case (a, b) => a + b 
    } 
} 

Każda z wersji zapewni zachowanie, które chcesz.

+0

Dzięki, to działa dobrze, także mój bardziej złożony przypadek ;-) Ale nadal mam problem z brakującą ukrytą wartością dla Mapper. Spróbuję rozwiązać to sam, ale może ty możesz mi w tym pomóc? – Klinke

+0

OK, znalazłem rozwiązanie dla uproszczonej wersji, dodając kontekst związany z literą A, aby pomóc. Więc mam teraz 'implicit def caseTuple [_t, A <: Foo {type T = _T} <% Foo {type T = _T}] = ... Tym razem rozwiązanie nie przetłumaczyło się tak łatwo do mojej pełnej wersji , ale mam nadzieję, że mogę naprawić nowe problemy. – Klinke

+0

@Klinke: Nie jestem pewien, czy rozumiem problem - jeśli skopiuję i wkleję moje 'add' do twojego' ExampleShapeless' wszystko wydaje się działać zgodnie z oczekiwaniami. –