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
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