Zgodnie z tym pytaniem system typów Scali jest kompletny w Turingu . Jakie zasoby są dostępne, aby nowicjusz mógł skorzystać z możliwości programowania na poziomie typu?
Oto zasoby, które do tej pory znalazłem:
- Wielka Czarodziejstwo Daniela Śpiewaka w krainie Scala
- Programowanie na poziomie typu Apocalisp w Scali
- Jesper's HList
Te zasoby są świetne, ale wydaje mi się, że brakuje mi podstaw, więc nie mam solidnych podstaw, na których można by budować. Na przykład, gdzie jest wprowadzenie do definicji typów? Jakie operacje mogę wykonywać na typach?
Czy są jakieś dobre materiały wprowadzające?
Odpowiedzi:
Przegląd
Programowanie na poziomie typu ma wiele podobieństw z tradycyjnym programowaniem na poziomie wartości. Jednak w przeciwieństwie do programowania na poziomie wartości, w którym obliczenia są wykonywane w czasie wykonywania, w programowaniu na poziomie typu obliczenia są wykonywane w czasie kompilacji. Spróbuję narysować podobieństwa między programowaniem na poziomie wartości a programowaniem na poziomie typu.
Paradygmaty
Istnieją dwa główne paradygmaty programowania na poziomie typu: „obiektowy” i „funkcjonalny”. Większość przykładów, do których prowadzą linki, jest zgodnych z paradygmatem zorientowanym obiektowo.
Dobry, dość prosty przykład programowania na poziomie typu w paradygmacie zorientowanym obiektowo można znaleźć w implementacji rachunku lambda w apokalisie , zreplikowanym tutaj:
Jak widać na przykładzie, paradygmat zorientowany obiektowo w programowaniu na poziomie typu przebiega następująco:
trait Lambda
, że gwarancje, że istnieją następujące typy:subst
,apply
, ieval
.trait App extends Lambda
sparametryzowane za pomocą dwóch typów (S
iT
oba muszą być podtypamiLambda
),trait Lam extends Lambda
sparametryzowane za pomocą jednego typu (T
) itrait X extends Lambda
(które nie są sparametryzowane).#
(który jest bardzo podobny do operatora kropki:.
dla wartości). W cechyApp
przykładu lambda nazębnego, rodzajueval
realizowana jest w następujący sposób:type eval = S#eval#apply[T]
. Zasadniczo jest to wywołanieeval
typu parametru cechyS
i wywołanie wynikuapply
z parametremT
. Uwaga,S
na pewno maeval
typ, ponieważ parametr określa, że jest to podtypLambda
. Podobnie, wynikeval
musi miećapply
typ, ponieważ jest określony jako podtypLambda
, zgodnie z definicją cechy abstrakcyjnejLambda
.Paradygmat funkcjonalny polega na definiowaniu wielu sparametryzowanych konstruktorów typów, które nie są zgrupowane razem w cechy.
Porównanie między programowaniem na poziomie wartości a programowaniem na poziomie typu
abstract class C { val x }
trait C { type X }
C.x
(odniesienie do wartości pola / funkcji x w obiekcie C)C#x
(odnoszący się do pola typu x w cechy C)def f(x:X) : Y
type f[x <: X] <: Y
(nazywa się to „konstruktorem typu” i zwykle występuje w cechy abstrakcyjnej)def f(x:X) : Y = x
type f[x <: X] = x
a:A == b:B
implicitly[A =:= B]
assert(a == b)
implicitly[A =:= B]
A <:< B
, kompiluje się tylko wtedy, gdyA
jest podtypemB
A =:= B
, kompiluje się tylko wtedy, gdyA
jest podtypemB
iB
jest podtypemA
A <%< B
, („widoczny jako”) kompiluje się tylko wtedy, gdyA
jest widoczny jakoB
(tj. istnieje niejawna konwersja zA
na podtypB
)Konwersja między typami i wartościami
W wielu przykładach typy zdefiniowane za pomocą cech są często zarówno abstrakcyjne, jak i zapieczętowane, w związku z czym nie można ich utworzyć bezpośrednio ani za pośrednictwem anonimowej podklasy. Dlatego często używa się go
null
jako wartości zastępczej podczas wykonywania obliczeń na poziomie wartości przy użyciu pewnego rodzaju zainteresowania:val x:A = null
gdzieA
jest typ , na którym Ci zależyZe względu na wymazywanie typów sparametryzowane typy wyglądają tak samo. Ponadto (jak wspomniano powyżej) wartości, z którymi pracujesz, zwykle są
null
, więc warunkowanie typu obiektu (np. Za pomocą instrukcji match) jest nieskuteczne.Sztuczka polega na użyciu niejawnych funkcji i wartości. Przypadek podstawowy jest zwykle wartością niejawną, a przypadek rekurencyjny jest zwykle funkcją niejawną. Rzeczywiście, programowanie na poziomie typu w dużym stopniu wykorzystuje implikacje.
Rozważmy ten przykład ( zaczerpnięty z metascala i apocalisp ):
Tutaj masz kodowanie peano liczb naturalnych. Oznacza to, że masz typ dla każdej nieujemnej liczby całkowitej: specjalny typ dla 0, a mianowicie
_0
; a każda liczba całkowita większa od zera ma typ formularzaSucc[A]
, gdzieA
jest typem reprezentującym mniejszą liczbę całkowitą. Na przykład typem reprezentującym 2 byłby:Succ[Succ[_0]]
(następca zastosowany dwukrotnie do typu reprezentującego zero).Możemy aliasować różne liczby naturalne dla wygodniejszego odniesienia. Przykład:
(To jest bardzo podobne do definiowania a
val
jako wyniku funkcji).Teraz przypuśćmy, że chcemy zdefiniować funkcję na poziomie wartości,
def toInt[T <: Nat](v : T)
która przyjmuje wartość argumentuv
, która jest zgodna zNat
i zwraca liczbę całkowitą reprezentującą liczbę naturalną zakodowaną wv
typie. Na przykład, jeśli mamy wartośćval x:_3 = null
(null
typuSucc[Succ[Succ[_0]]]
), chcielibyśmytoInt(x)
zwrócić3
.Aby zaimplementować
toInt
, wykorzystamy następującą klasę:Jak zobaczymy poniżej,
TypeToValue
dla każdego z klasNat
od_0
do (np.) Powstanie obiekt zbudowany z klasy_3
, a każdy z nich będzie przechowywać reprezentację wartości odpowiedniego typu (tj.TypeToValue[_0, Int]
Będzie przechowywać wartość0
,TypeToValue[Succ[_0], Int]
będzie przechowywać wartość1
itp.). Uwaga,TypeToValue
jest parametryzowana przez dwa typy:T
iVT
.T
odpowiada typowi, do którego próbujemy przypisać wartości (w naszym przykładzieNat
) iVT
odpowiada typowi wartości, który mu przypisujemy (w naszym przykładzieInt
).Teraz tworzymy następujące dwie niejawne definicje:
I realizujemy
toInt
w następujący sposób:Aby zrozumieć, jak to
toInt
działa, zastanówmy się, co robi na kilku wejściach:Kiedy wywołujemy
toInt(z)
, kompilator szuka niejawnego argumentuttv
typuTypeToValue[_0, Int]
(ponieważz
jest typu_0
). Znajduje obiekt_0ToInt
, wywołujegetValue
metodę tego obiektu i wraca0
. Ważną kwestią, na którą należy zwrócić uwagę, jest to, że nie wskazaliśmy programowi, którego obiektu użyć, kompilator znalazł to niejawnie.Rozważmy teraz
toInt(y)
. Tym razem kompilator szuka niejawnego argumentuttv
typuTypeToValue[Succ[_0], Int]
(ponieważy
jest typuSucc[_0]
). Znajduje funkcjęsuccToInt
, która może zwrócić obiekt odpowiedniego typu (TypeToValue[Succ[_0], Int]
) i ocenia ją. Ta funkcja sama przyjmuje niejawny argument (v
) typuTypeToValue[_0, Int]
(to znaczy,TypeToValue
gdzie pierwszy parametr typu ma o jeden mniejSucc[_]
). Kompilator dostarcza_0ToInt
(tak jak zostało to zrobione przy ocenietoInt(z)
powyżej) isuccToInt
konstruuje nowyTypeToValue
obiekt z wartością1
. Ponownie, należy zauważyć, że kompilator udostępnia wszystkie te wartości w sposób niejawny, ponieważ nie mamy do nich jawnego dostępu.Sprawdzam twoją pracę
Istnieje kilka sposobów sprawdzenia, czy obliczenia na poziomie typu działają zgodnie z oczekiwaniami. Oto kilka podejść. Zrób dwa typy
A
iB
, które chcesz zweryfikować, są równe. Następnie sprawdź, czy następująca kompilacja:Equal[A, B]
Equal[T1 >: T2 <: T2, T2]
( pobrane z apocolisp )implicitly[A =:= B]
Alternatywnie możesz przekonwertować typ na wartość (jak pokazano powyżej) i sprawdzić wartości w czasie wykonywania. Np.
assert(toInt(a) == toInt(b))
, Gdziea
jest typuA
ib
jest typuB
.Dodatkowe zasoby
Pełny zestaw dostępnych konstrukcji można znaleźć w sekcji typów podręcznika referencyjnego skali (pdf) .
Adriaan Moors ma kilka artykułów naukowych na temat konstruktorów typów i powiązanych tematów z przykładami ze scala:
Apocalisp to blog z wieloma przykładami programowania na poziomie typów w scali.
ScalaZ to bardzo aktywny projekt, który zapewnia funkcjonalność rozszerzającą interfejs API Scala przy użyciu różnych funkcji programowania na poziomie typu. To bardzo ciekawy projekt, który cieszy się dużym zainteresowaniem.
MetaScala jest biblioteką na poziomie typów dla Scali, zawierającą metatypy dla liczb naturalnych, wartości logicznych, jednostek, HList itp. Jest to projekt Jespera Nordenberga (jego blog) .
Michid (blog) ma kilka świetnych przykładów programowania typu poziom w Scali (z drugiej odpowiedź):
Debasish Ghosh (blog) również ma kilka odpowiednich postów:
(Prowadziłem badania na ten temat i oto czego się dowiedziałem. Nadal jestem w tym nowy, więc proszę wskazać wszelkie nieścisłości w tej odpowiedzi.)
źródło
Oprócz innych linków tutaj, są też moje posty na blogu na temat programowania meta na poziomie typu w Scali:
źródło
Jak zasugerowano na Twitterze: Bezkształtny: Eksploracja programowania ogólnego / politypowego w Scali autorstwa Milesa Sabina.
źródło
źródło
Scalaz ma kod źródłowy, wiki i przykłady.
źródło