Czasami napotykam na pół tajemniczą notację
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
w postach na blogu Scali, które dają mu falę ręczną „użyliśmy tego typu sztuczki lambda”.
Chociaż mam pewne wyobrażenia na ten temat (uzyskujemy anonimowy parametr typu A
bez konieczności zanieczyszczania nim definicji?), Nie znalazłem jasnego źródła opisującego, czym jest sztuczka lambda i jakie są jej zalety. Czy to tylko cukier syntaktyczny, czy może otwiera nowe wymiary?
Odpowiedzi:
Lambdy typu są niezbędne przez większość czasu, gdy pracujesz z typami wyższego rzędu.
Rozważmy prosty przykład definiowania monady dla prawej projekcji Albo [A, B]. Typeklasa monada wygląda następująco:
Teraz Either jest konstruktorem typu z dwoma argumentami, ale aby zaimplementować Monadę, musisz nadać jej konstruktor typu z jednym argumentem. Rozwiązaniem tego problemu jest użycie typu lambda:
Oto przykład curry w systemie typów - masz już wybrany typ Either, tak że gdy chcesz utworzyć instancję EitherMonad, musisz określić jeden z typów; drugi oczywiście jest dostarczany w momencie wywołania punktu lub wiązania.
Sztuczka lambda typu wykorzystuje fakt, że pusty blok w pozycji typu tworzy anonimowy typ strukturalny. Następnie używamy składni #, aby uzyskać składową typu.
W niektórych przypadkach możesz potrzebować bardziej wyrafinowanych lambd typu, których pisanie w tekście jest trudne. Oto przykład z mojego dzisiejszego kodu:
Ta klasa istnieje wyłącznie po to, że mogę użyć nazwy takiej jak FG [F, G] #IterateeM, aby odnieść się do typu monady IterateeT wyspecjalizowanej w jakiejś wersji transformatorowej drugiej monady, która jest wyspecjalizowana w jakiejś trzeciej monadzie. Kiedy zaczynasz się układać, tego rodzaju konstrukcje stają się bardzo potrzebne. Oczywiście nigdy nie tworzę instancji FG; jest to tylko sztuczka, która pozwala mi wyrazić to, czego chcę w systemie typów.
źródło
bind
metodę dla swojejEitherMonad
klasy. :-) Poza tym, jeśli mogę skierować Adriaana na sekundę tutaj, w tym przykładzie nie używasz typów wyższego rzędu. Jesteś wFG
, ale nieEitherMonad
. Zamiast tego używasz konstruktorów typów , które mają rodzaj* => *
. Ten rodzaj jest rzędu 1, który nie jest „wyższy”.*
to porządek-1, ale w każdym razie Monada ma(* => *) => *
. Zauważysz również, że określiłem „właściwą projekcjęEither[A, B]
” - implementacja jest trywialna (ale dobre ćwiczenie, jeśli nie robiłeś tego wcześniej!)*=>*
wyższego rzędu, jest uzasadniony analogią, że nie nazywamy zwykłej funkcji (która odwzorowuje niefunkcje na niefunkcje, innymi słowy, zwykłe wartości na zwykłe wartości) funkcją wyższego rzędu.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Korzyści są dokładnie takie same, jak te zapewniane przez funkcje anonimowe.
Przykładowe użycie w Scalaz 7. Chcemy użyć funkcji,
Functor
która może odwzorować funkcję na drugi element w plikuTuple2
.Scalaz zapewnia pewne niejawne konwersje, do których można wywnioskować argument typu
Functor
, więc często całkowicie unikamy ich pisania. Poprzedni wiersz można przepisać jako:Jeśli używasz IntelliJ, możesz włączyć Ustawienia, Styl kodu, Scala, Składanie, Typ Lambdy. To następnie ukrywa crufty części składni i przedstawia bardziej smaczne:
Przyszła wersja Scali może bezpośrednio obsługiwać taką składnię.
źródło
(1, 2).map(a => a + 1)
w REPL wystąpił błąd : `` <console>: 11: error: mapowanie wartości nie jest członkiem (Int, Int) (1, 2) .map (a => a + 1) ^ ''Aby nadać kontekst: ta odpowiedź została pierwotnie opublikowana w innym wątku. Widzisz to tutaj, ponieważ dwa wątki zostały połączone. Pytanie we wspomnianym wątku brzmiało następująco:
Odpowiedź:
Jeden
P
znak podkreślenia w polach po nim oznacza, że jest to konstruktor typu, który przyjmuje jeden typ i zwraca inny typ. Przykłady konstruktorów typu z tego rodzaju:List
,Option
.Daj , rodzaj betonu, a to daje , inny rodzaj betonu. Daj i to daje . Itp.
List
Int
List[Int]
List
String
List[String]
Tak więc
List
,Option
może być uznana za funkcje Wysokość Rodzaj liczbę operandów 1. Formalnie mówimy, mają rodzaju* -> *
. Gwiazdka oznacza typ.Teraz
Tuple2[_, _]
jest konstruktorem typu z rodzajem,(*, *) -> *
tzn. Musisz nadać mu dwa typy, aby uzyskać nowy typ.Ponieważ ich podpisy nie pasują, nie można zastąpić
Tuple2
zaP
. To, co musisz zrobić, to częściowo zastosowaćTuple2
jeden z jego argumentów, co da nam konstruktor typu z rodzajem* -> *
i możemy go zastąpićP
.Niestety Scala nie ma specjalnej składni do częściowego stosowania konstruktorów typów, więc musimy uciekać się do potworności zwanej lambdami typów. (To, co masz w swoim przykładzie). Nazywa się je tak, ponieważ są analogiczne do wyrażeń lambda, które istnieją na poziomie wartości.
Poniższy przykład może pomóc:
Edytować:
Więcej równoległych poziomów wartości i typów.
W przypadku, który przedstawiłeś, parametr typu
R
jest lokalny dla funkcji,Tuple2Pure
więc nie możesz go po prostu zdefiniowaćtype PartialTuple2[A] = Tuple2[R, A]
, ponieważ po prostu nie ma miejsca, w którym możesz umieścić ten synonim.Aby poradzić sobie z takim przypadkiem, używam następującej sztuczki, która wykorzystuje składowe typu. (Mam nadzieję, że przykład jest oczywisty).
źródło
type World[M[_]] = M[Int]
powoduje, że cokolwiek umieścimyA
w jest zawsze prawdziwe chyba.X[A]
implicitly[X[A] =:= Foo[String,Int]]
źródło