2015-08-09 12 views
10

w Scala, następujący kod kompiluje poprawnie:niejawne klasy konwersji na typ-alias typy funkcyjne nie skompilować w Scala

class a {} 
class b {} 

object Main { 

    implicit class Conv[f, t](val v: f ⇒ t) extends AnyVal { 
    def conv = v 
    } 

    def main(args: Array[String]) { 
    val m = (a: a) ⇒ new b 
    m.conv 
    } 
} 

Ale z jakiegoś powodu następującej nie kompilacji:

class a {} 
class b {} 

object Main { 
    type V[f, t] = f ⇒ t 

    implicit class Conv[f, t](val v: V[f, t]) extends AnyVal { 
    def conv = v 
    } 

    def main(args: Array[String]) { 
    val m = (a: a) ⇒ new b 
    m.conv 
    } 
} 

z następującym komunikatem:

value conv is not a member of a => b 
    m.conv 

Dlaczego tak się dzieje?

EDIT: Tak, wciąż jest błąd nawet z

val m: V[a,b] = new V[a,b] { def apply(a: a) = new b } 
+3

dobrze, można rozwiązać ten problem przez zadeklarowanie wariancji parametru funkcji w aliasu typu: 'typu V [-f, t] = f ⇒ t' – Kolmar

+0

cóż, nie masz niejawnej konwersji z '(a => b)' na 'Conv [a, b]', więc najpierw pomyślałem, że przypisanie typu powinno pomóc 'val m: V [a, b] = (a: a) ⇒ nowy b'. Ale jak się okazuje, to nadal nie kompiluje zmiany błędu na "wartość conv nie jest członkiem V [a, b] | m.conv' (choć IntelliJ Idea znajduje niejawną konwersję). –

Odpowiedz

4

w pierwszym przykładzie, val v: f => t wnioskuje do podpisu [-A, +B] typu, ponieważ jest skrótem funkcji jednego parametru. Function1 ma podpis typu, Function1[-A, +B]. Tak więc typ, który jest Contravariant w parametrze A i Covariant w parametrze B.

Następnie funkcja lambda, (a: a) => new b później w kodzie, ma swój typ wywnioskowany jako funkcja od a do b. Tak więc podpis typu jest identyczny, a domyślna rozdzielczość działa.

W drugim przykładzie wpisz V[f, t] = f => t i utworzony z niego parametr: val v: V[f, t], ich typ jest wyraźnie określony jako V[f, t]. Funkcja f => t nadal będzie miała postać [-A, +B], ale wyraźnie ograniczysz typy do postaci niezmiennej w sygnaturze typu, więc typ V jest niezmienny w obu typach parametrów.

Później, gdy zadeklarujesz: val m = (a: a) => new b, podpis typu będzie nadal [-A, +B], jak w pierwszym przykładzie. Implicit resolution nie działa, ponieważ val jest Contravariant w pierwszym parametrze, ale typ V jest Invariant w swoim pierwszym parametrze.

Zmiana sygnatury typu V na V[-f, +t] lub V[-f, t] rozwiązuje tę i domyślną rozdzielczość działa jeszcze raz.

Podnosi to pytanie, dlaczego kowariancja parametru drugiego typu nie stanowi problemu dla niejawnego rozwiązania, podczas gdy Kontrawencja pierwszego parametru typu to. Bawiłem się i wykonałem trochę badań. Natknąłem się na kilka interesujących linków, co wskazuje na to, że istnieją pewne ograniczenia/problemy dotyczące domyślnego rozwiązania, szczególnie w przypadku kontrawariancji.

będę musiał odroczyć do kogoś ze znajomością budowy wewnętrznej kompilator Scala i mechaniki rozdzielczości Aby uzyskać więcej szczegółów, ale wydaje się, że ta działa wbrew niektórym ograniczeniom w zakresie dorozumianego rozwiązywania w kontekście Kontrawariancji.

Na swoim trzecim przykładzie, myślę, że chodziło Ci o:

class a {} 
class b {} 

object Main { 
    type V[f, t] = f => t 

    implicit class Conv[f, t](val v: V[f, t]) extends AnyVal { 
    def conv = v 
    } 

    def main(args: Array[String]) { 
    val m: V[a,b] = new V[a,b] { def apply(a: a) = new b } 
    m.conv // does not compile 
    } 
} 

ten jest interesujący i myślę, że jest to nieco inna przyczyna. Aliasy typu są ograniczone pod względem tego, co mogą zmienić, jeśli chodzi o wariancję, ale dopuszczenie wariancji jest surowsze. Nie mogę powiedzieć na pewno, co się tutaj dzieje, ale tutaj jest interesting Stack Overflow question related to type aliases and variance.

Biorąc pod uwagę złożoność niejawnej rozdzielczości, w połączeniu z dodatkowymi współczynnikami wariancji deklaracji typu w stosunku do domyślnej wariancji Function1, podejrzewam, że kompilator nie jest w stanie rozwiązać problemu w tym konkretnym scenariuszu.

Zmiana na:

implicit class Conv(val v: V[_, _]) extends AnyVal { 
    def conv = v 
} 

Oznacza to, że działa we wszystkich scenariuszach, bo to w zasadzie mówiąc do kompilatora, że ​​dla celów niejawnego klasy Conv, nie dbają o wariancji na parametry typu V.

np dodaje działa również

class a {} 
class b {} 

object Main { 
    type V[f, t] = f ⇒ t 

    implicit class Conv(val v: V[_, _]) extends AnyVal { 
    def conv = v 
    } 

    def main(args: Array[String]) { 
    val m = (a: a) ⇒ new b 
    m.conv 
    } 
} 
+0

Dziękuję, to jest świetna odpowiedź, podejrzewałem, że ma to coś wspólnego z wariancją. Wszystkie te fakty nie odpowiadają na komentarz mojej redakcji, mianowicie dlaczego jawnie tworzyłem obiekt V [f, t] ('val m: V [a, b] = new V [a, b] {def apply (a: a) = new b} ') nadal nie jest niejawnie konwertowane na' Conv'. Podejrzewam, że to błąd i nie ma prawdziwej odpowiedzi, ale na wszelki wypadek poczekam trochę więcej na ten temat. Czy może czegoś brakuje? –

+0

Dodałem trochę aktualizacji, aby zaadresować edycję –

Powiązane problemy