Menurut pertanyaan ini , sistem tipe Scala adalah Turing lengkap . Sumber daya apa yang tersedia yang memungkinkan pendatang baru untuk memanfaatkan kekuatan pemrograman tingkat tipe?
Berikut adalah sumber daya yang saya temukan sejauh ini:
- Sihir Tinggi Daniel Spiewak di Negeri Scala
- Pemrograman Tingkat-Jenis Apocalisp di Scala
- HList Jesper
Sumber daya ini bagus, tetapi saya merasa seperti kehilangan dasar-dasarnya, sehingga tidak memiliki fondasi yang kuat untuk membangun. Misalnya, di mana ada pengenalan definisi tipe? Operasi apa yang dapat saya lakukan pada tipe?
Apakah ada sumber pengantar yang bagus?
Jawaban:
Gambaran
Pemrograman tingkat-tipe memiliki banyak kesamaan dengan pemrograman tingkat-nilai tradisional. Namun, tidak seperti pemrograman tingkat nilai, di mana komputasi terjadi pada waktu proses, dalam pemrograman tingkat-tipe, komputasi terjadi pada waktu kompilasi. Saya akan mencoba menarik kesejajaran antara pemrograman pada tingkat nilai dan pemrograman pada tingkat tipe.
Paradigma
Ada dua paradigma utama dalam pemrograman level-tipe: "berorientasi objek" dan "fungsional". Sebagian besar contoh yang ditautkan dari sini mengikuti paradigma berorientasi objek.
Contoh yang bagus dan cukup sederhana dari pemrograman level-tipe dalam paradigma berorientasi objek dapat ditemukan dalam implementasi apocalisp dari kalkulus lambda , yang direplikasi di sini:
Seperti dapat dilihat pada contoh, paradigma berorientasi objek untuk pemrograman level-tipe berlangsung sebagai berikut:
trait Lambda
menjamin bahwa jenis berikut ada:subst
,apply
, daneval
.trait App extends Lambda
yang diparameterisasi dengan dua tipe (S
danT
, keduanya harus merupakan subtipe dariLambda
),trait Lam extends Lambda
diparameterisasi dengan satu tipe (T
), dantrait X extends Lambda
(yang tidak berparameterisasi).#
(yang sangat mirip dengan operator titik:.
untuk nilai). Dalam sifatApp
dari contoh lambda kalkulus, jeniseval
diimplementasikan sebagai berikut:type eval = S#eval#apply[T]
. Ini pada dasarnya memanggileval
jenis parameter sifatS
, dan memanggilapply
dengan parameterT
pada hasil. Catatan,S
dijamin memilikieval
tipe karena parameter menetapkannya sebagai subtipe dariLambda
. Demikian pula, hasil darieval
must have aapply
type, karena itu ditentukan sebagai subtipe dariLambda
, seperti yang ditentukan dalam sifat abstrakLambda
.Paradigma fungsional terdiri dari banyak definisi konstruktor tipe berparameter yang tidak dikelompokkan bersama dalam sifat.
Perbandingan antara pemrograman tingkat nilai dan pemrograman tingkat tipe
abstract class C { val x }
trait C { type X }
C.x
(mereferensikan nilai bidang / fungsi x di objek C)C#x
(mereferensikan tipe bidang x pada sifat C)def f(x:X) : Y
type f[x <: X] <: Y
(ini disebut "tipe konstruktor" dan biasanya terjadi dalam sifat abstrak)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
, mengkompilasi hanya jikaA
merupakan subtipe dariB
A =:= B
, mengkompilasi hanya jikaA
adalah subtipe dariB
danB
merupakan subtipe dariA
A <%< B
, ("terlihat sebagai") mengkompilasi hanya jikaA
dapat dilihat sebagaiB
(yaitu ada konversi implisit dariA
ke subtipe dariB
)Mengonversi antara tipe dan nilai
Dalam banyak contoh, tipe yang didefinisikan melalui ciri-ciri seringkali bersifat abstrak dan tertutup, dan oleh karena itu tidak dapat dibuat instance-nya secara langsung atau melalui subkelas anonim. Jadi, biasanya digunakan
null
sebagai nilai placeholder saat melakukan penghitungan tingkat nilai menggunakan beberapa jenis minat:val x:A = null
, di manaA
tipe yang Anda pedulikanKarena tipe-erasure, semua tipe berparameter terlihat sama. Selain itu, (seperti yang disebutkan di atas) nilai yang Anda kerjakan cenderung semuanya
null
, dan karena itu pengondisian pada tipe objek (misalnya melalui pernyataan kecocokan) tidak efektif.Triknya adalah dengan menggunakan fungsi dan nilai implisit. Kasus dasar biasanya berupa nilai implisit dan kasus rekursif biasanya merupakan fungsi implisit. Memang, pemrograman level-tipe membuat banyak penggunaan implikasinya.
Pertimbangkan contoh ini ( diambil dari metascala dan apocalisp ):
Di sini Anda memiliki pengkodean peano dari bilangan asli. Artinya, Anda memiliki tipe untuk setiap bilangan bulat non-negatif: tipe khusus untuk 0, yaitu
_0
; dan setiap bilangan bulat yang lebih besar dari nol memiliki jenis formulirSucc[A]
, di manaA
jenis yang mewakili bilangan bulat yang lebih kecil. Misalnya, tipe yang mewakili 2 adalah:Succ[Succ[_0]]
(penerus diterapkan dua kali ke tipe yang mewakili nol).Kami dapat membuat alias berbagai bilangan asli untuk referensi yang lebih nyaman. Contoh:
(Ini sangat mirip dengan mendefinisikan a
val
sebagai hasil dari suatu fungsi.)Sekarang, misalkan kita ingin mendefinisikan fungsi tingkat-nilai
def toInt[T <: Nat](v : T)
yang mengambil nilai argumenv
,, yang sesuai denganNat
dan mengembalikan integer yang mewakili bilangan asli yang dikodekan dalamv
tipe. Misalnya, jika kita memiliki nilaival x:_3 = null
(null
tipeSucc[Succ[Succ[_0]]]
), kita ingintoInt(x)
mengembalikan3
.Untuk menerapkannya
toInt
, kita akan menggunakan kelas berikut:Seperti yang akan kita lihat di bawah, akan ada objek yang dibangun dari kelas
TypeToValue
untuk masing-masingNat
dari_0
atas hingga (misalnya)_3
, dan masing-masing akan menyimpan representasi nilai dari tipe yang sesuai (yaituTypeToValue[_0, Int]
akan menyimpan nilai0
,TypeToValue[Succ[_0], Int]
akan menyimpan nilai1
, dll.). Catatan,TypeToValue
diparameterisasi oleh dua jenis:T
danVT
.T
sesuai dengan jenis yang kita coba tetapkan nilainya (dalam contoh kita,Nat
) danVT
sesuai dengan jenis nilai yang kita tetapkan padanya (dalam contoh kita,Int
).Sekarang kita membuat dua definisi implisit berikut:
Dan kami menerapkan
toInt
sebagai berikut:Untuk memahami cara
toInt
kerjanya, mari pertimbangkan apa yang dilakukannya pada beberapa input:Saat kita memanggil
toInt(z)
, kompilator mencari argumen implisitttv
bertipeTypeToValue[_0, Int]
(karenaz
bertipe_0
). Ia menemukan objek_0ToInt
, ia memanggilgetValue
metode objek ini dan kembali0
. Hal penting yang perlu diperhatikan adalah bahwa kita tidak menentukan objek mana yang akan digunakan pada program, kompilator menemukannya secara implisit.Sekarang mari kita pertimbangkan
toInt(y)
. Kali ini, kompilator mencari argumen implisitttv
bertipeTypeToValue[Succ[_0], Int]
(karenay
bertipeSucc[_0]
). Ia menemukan fungsisuccToInt
, yang bisa mengembalikan objek dari type (TypeToValue[Succ[_0], Int]
) yang sesuai dan mengevaluasinya. Fungsi ini sendiri mengambil argumen implisit (v
) dari tipeTypeToValue[_0, Int]
(yaitu, diTypeToValue
mana parameter tipe pertama memiliki satu lebih sedikitSucc[_]
). Kompilator menyediakan_0ToInt
(seperti yang dilakukan dalam evaluasi ditoInt(z)
atas), dansuccToInt
membangunTypeToValue
objek baru dengan nilai1
. Sekali lagi, penting untuk dicatat bahwa kompilator menyediakan semua nilai ini secara implisit, karena kita tidak memiliki akses secara eksplisit.Memeriksa pekerjaan Anda
Ada beberapa cara untuk memverifikasi bahwa penghitungan tingkat tipe Anda melakukan apa yang Anda harapkan. Berikut ini beberapa pendekatan. Buat dua jenis
A
danB
, yang ingin Anda verifikasi adalah sama. Kemudian periksa kompilasi berikut:Equal[A, B]
Equal[T1 >: T2 <: T2, T2]
( diambil dari apocolisp )implicitly[A =:= B]
Alternatifnya, Anda dapat mengonversi jenis menjadi nilai (seperti yang ditunjukkan di atas) dan melakukan pemeriksaan waktu proses terhadap nilai tersebut. Misalnya
assert(toInt(a) == toInt(b))
, di manaa
tipeA
danb
tipeB
.Sumber daya tambahan
Kumpulan lengkap dari konstruksi yang tersedia dapat ditemukan di bagian tipe dari manual referensi skala (pdf) .
Adriaan Moors memiliki beberapa makalah akademis tentang konstruktor tipe dan topik terkait dengan contoh dari scala:
Apocalisp adalah blog dengan banyak contoh pemrograman level-tipe dalam skala.
ScalaZ adalah proyek yang sangat aktif yang menyediakan fungsionalitas yang memperluas API Scala menggunakan berbagai fitur pemrograman tingkat tipe. Ini adalah proyek yang sangat menarik yang memiliki banyak pengikut.
MetaScala adalah pustaka tingkat jenis untuk Scala, termasuk jenis meta untuk bilangan asli, boolean, unit, HList, dll. Ini adalah proyek oleh Jesper Nordenberg (blognya) .
The Michid (blog) memiliki beberapa contoh yang mengagumkan dari pemrograman jenis-tingkat di Scala (dari jawaban lain):
Debasish Ghosh (blog) juga memiliki beberapa posting yang relevan:
(Saya telah melakukan beberapa penelitian tentang subjek ini dan inilah yang telah saya pelajari. Saya masih baru dalam hal itu, jadi harap tunjukkan ketidakakuratan dalam jawaban ini.)
sumber
Selain link lain disini, ada juga postingan blog saya tentang tipe level meta programming di Scala:
sumber
Seperti yang disarankan di Twitter: Shapeless: Eksplorasi pemrograman generik / polytypic di Scala oleh Miles Sabin.
sumber
sumber
Scalaz memiliki kode sumber, wiki dan contoh.
sumber