Udało Ci się uniknąć irytacji: SI-2712. Dla jasności, mam zamiar zminimalizować kod nieco:
import language.higherKinds
object Test {
case class Base[A](a: A)
case class Recursive[F[_], A](fa: F[A])
def main(args: Array[String]): Unit = {
val one = Base(1)
val two = Recursive(one)
val three = Recursive(two) // doesn't compile
println(three)
}
}
To pokazuje ten sam błąd typu jak Twoja:
argument expression's type is not compatible with formal parameter type;
found : Test.Recursive[Test.Base,Int]
required: ?F
val three = Recursive(two) // doesn't compile
^
Najpierw trochę składni i terminologii pewnie już wiedzą:
- W Scali mówimy, że zwykły, nieparametryczny typ danych (taki jak
Int
) ma rodzaj _
. Jest to monomorficzna.
Base
, z drugiej strony, jest sparametryzowany. nie możemy używać go jako typu wartości bez podania typu, który zawiera, więc mówimy, że ma rodzaj _[_]
. Jest to ranga-1 polimorficzna: konstruktor typu, który przyjmuje typ.
Recursive
idzie dalej: ma dwa parametry: F[_]
i A
. Liczba parametrów typu nie ma tutaj znaczenia, ale ich rodzaje to robią. F[_]
jest polimorficzny 1 stopnia, więc Recursive
jest rangowy 2 polimorficzny: jest to konstruktor typu, który przyjmuje konstruktor typu.
- Nazywamy wszystko rangą 2 lub wyższą wyższym krojem, i tu zaczyna się zabawa.
Scala w ogóle nie ma problemów z typami o wyższym typie. Jest to jedna z kilku kluczowych cech, która odróżnia jego typ systemu od, powiedzmy, języka Java. Ale ma problem z częściową aplikacją parametrów typu, gdy mamy do czynienia z typami wyższego rzędu.
Oto problem: Recursive[F[_], A]
ma dwa parametry typu.W kodzie przykładowym ty zrobił „typ lambda” trick częściowo zastosować pierwszy parametr, coś jak:
val one = Base(1)
val two = Recursive(one)
val three = {
type λ[α] = Recursive[Base, α]
Recursive(two : λ[Int])
}
Ten przekonuje kompilator, że jesteś dostarczając coś właściwego rodzaju (_[_]
) do Recursive
konstruktor. Jeśli Scala miał curry listy parametrów typu, Zdecydowanie wykorzystali że tutaj:
case class Base[A](a: A)
case class Recursive[F[_]][A](fa: F[A]) // curried!
def main(args: Array[String]): Unit = {
val one = Base(1) // Base[Int]
val two = Recursive(one) // Recursive[Base][Int]
val three = Recursive(two) // Recursive[Recursive[Base]][Int]
println(three)
}
Niestety, nie (patrz SI-4719). Tak więc, zgodnie z moją najlepszą wiedzą, najczęstszym sposobem radzenia sobie z tym problemem jest "nieuczciwa sztuczka", z powodu Milesa Sabina. Tutaj jest znacznie uproszczona wersja tego, co pojawia się w scalaz:
import language.higherKinds
trait Unapply[FA] {
type F[_]
type A
def apply(fa: FA): F[A]
}
object Unapply {
implicit def unapply[F0[_[_], _], G0[_], A0] = new Unapply[F0[G0, A0]] {
type F[α] = F0[G0, α]
type A = A0
def apply(fa: F0[G0, A0]): F[A] = fa
}
}
W nieco ręcznie wavey warunkach, to Unapply
konstrukt jest jak „pierwszej klasy typu lambda.” Definiujemy cechę reprezentującą twierdzenie, że jakiś typ FA
może zostać rozłożony na konstruktor typu F[_]
i typ A
. Następnie w jego obiekcie towarzyszącym możemy zdefiniować implikacje, aby zapewnić określone dekompozycje dla typów różnego rodzaju. Zdefiniowałem tutaj tylko ten konkretny, który musimy dopasować, ale możesz napisać inne.
Dzięki tej dodatkowej odrobiny kanalizacji, możemy teraz zrobić to, czego potrzebujemy:
import language.higherKinds
object Test {
case class Base[A](a: A)
case class Recursive[F[_], A](fa: F[A])
object Recursive {
def apply[FA](fa: FA)(implicit u: Unapply[FA]) = new Recursive(u(fa))
}
def main(args: Array[String]): Unit = {
val one = Base(1)
val two = Recursive(one)
val three = Recursive(two)
println(three)
}
}
Ta-da! Teraz typowanie działa, a to się kompiluje. Jako ćwiczenie, którą proponujemy utworzyć dodatkową klasę:
case class RecursiveFlipped[A, F[_]](fa: F[A])
... co nie jest naprawdę różni się od Recursive
w żaden znaczący sposób, oczywiście, ale po raz kolejny przełamać typu wnioskowanie. Następnie określ dodatkową instalację hydrauliczną potrzebną do jej naprawy. Powodzenia!
Edit
Pytałeś o mniej uproszczonej wersji, coś świadomi typu zajęć. Pewna modyfikacja jest wymagana, ale mam nadzieję, że widzisz podobieństwo. Po pierwsze, oto nasze zmodernizowane Unapply
:
import language.higherKinds
trait Unapply[TC[_[_]], FA] {
type F[_]
type A
def TC: TC[F]
def apply(fa: FA): F[A]
}
object Unapply {
implicit def unapply[TC[_[_]], F0[_[_], _], G0[_], A0](implicit TC0: TC[({ type λ[α] = F0[G0, α] })#λ]) =
new Unapply[TC, F0[G0, A0]] {
type F[α] = F0[G0, α]
type A = A0
def TC = TC0
def apply(fa: F0[G0, A0]): F[A] = fa
}
}
Ponownie, jest to completely ripped off from scalaz.Teraz niektóre przykładowy kod używając go:
import language.{ implicitConversions, higherKinds }
object Test {
// functor type class
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
// functor extension methods
object Functor {
implicit class FunctorOps[F[_], A](fa: F[A])(implicit F: Functor[F]) {
def map[B](f: A => B) = F.map(fa)(f)
}
implicit def unapply[FA](fa: FA)(implicit u: Unapply[Functor, FA]) =
new FunctorOps(u(fa))(u.TC)
}
// identity functor
case class Id[A](value: A)
object Id {
implicit val idFunctor = new Functor[Id] {
def map[A, B](fa: Id[A])(f: A => B) = Id(f(fa.value))
}
}
// pair functor
case class Pair[F[_], A](lhs: F[A], rhs: F[A])
object Pair {
implicit def pairFunctor[F[_]](implicit F: Functor[F]) = new Functor[({ type λ[α] = Pair[F, α] })#λ] {
def map[A, B](fa: Pair[F, A])(f: A => B) = Pair(F.map(fa.lhs)(f), F.map(fa.rhs)(f))
}
}
def main(args: Array[String]): Unit = {
import Functor._
val one = Id(1)
val two = Pair(one, one) map { _ + 1 }
val three = Pair(two, two) map { _ + 1 }
println(three)
}
}
Może być ten sam problem jak [Strange błędu z wyższym kinded typów Scala 2.10.0 (działa ze scala 2.9.2)] (http://stackoverflow.com/questions/15265741/strange-error-with-higher-kinded-types-in-scala-2-10-0-works-with -scala-2-9-2) – EECOLOR
To może być również pytanie pokrewne: [Czy możliwe jest ulepszenie wnioskowania typu dla częściowo zastosowanych typów w Scali?] (Http://stackoverflow.com/questions/15294966/is-is-possible-to-improve-type- wnioskowanie-dla-częściowo-zastosowanych-typów-w-scala) – EECOLOR
Dzięki za pomocne wskazówki. Okazuje się, że 2.10.1-RC3 zachowuje się w ten sam sposób. –