Kadang saya tersandung ke dalam notasi semi-misterius
def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..}
dalam posting blog Scala, yang memberinya "kami menggunakan trik tipu-lambda".
Sementara saya memiliki beberapa intutisi tentang hal ini (kita mendapatkan parameter tipe anonim A
tanpa harus mencemari definisi dengan itu?), Saya tidak menemukan sumber yang jelas menggambarkan apa jenis trik lambda, dan apa manfaatnya. Apakah itu hanya gula sintaksis, atau apakah ia membuka beberapa dimensi baru?
Jawaban:
Tipe lambdas sangat vital saat Anda bekerja dengan tipe yang lebih tinggi.
Pertimbangkan contoh sederhana mendefinisikan monad untuk proyeksi yang tepat untuk [A, B]. Typeclass monad terlihat seperti ini:
Sekarang, Entah adalah konstruktor tipe dari dua argumen, tetapi untuk mengimplementasikan Monad, Anda harus memberikan konstruktor tipe dari satu argumen. Solusi untuk ini adalah dengan menggunakan tipe lambda:
Ini adalah contoh penjelajahan dalam sistem tipe - Anda telah menjelajah jenis Either, sehingga ketika Anda ingin membuat turunan dari EitherMonad, Anda harus menentukan salah satu jenis; yang lain tentu saja disediakan pada saat Anda menelepon atau mengikat.
Trik tipe lambda mengeksploitasi fakta bahwa blok kosong pada posisi tipe menciptakan tipe struktural anonim. Kami kemudian menggunakan sintaks # untuk mendapatkan anggota tipe.
Dalam beberapa kasus, Anda mungkin perlu jenis lambdas yang lebih canggih yang sulit untuk dituliskan. Ini contoh dari kode saya mulai hari ini:
Kelas ini ada secara eksklusif sehingga saya dapat menggunakan nama seperti FG [F, G] #IterateeM untuk merujuk pada jenis monad IterateeT yang khusus untuk beberapa versi transformator dari monad kedua yang dikhususkan untuk beberapa monad ketiga. Ketika Anda mulai menumpuk, konstruksi semacam ini menjadi sangat diperlukan. Saya tidak pernah instantiate FG, tentu saja; itu hanya ada sebagai retasan untuk membiarkan saya mengekspresikan apa yang saya inginkan dalam sistem tipe.
sumber
bind
metode untukEitherMonad
kelas Anda . :-) Selain itu, jika saya dapat menyalurkan Adriaan sebentar di sini, Anda tidak menggunakan jenis yang lebih tinggi dalam contoh itu. Anda berada diFG
, tetapi tidak diEitherMonad
. Sebaliknya, Anda menggunakan konstruktor tipe , yang memiliki jenis* => *
. Jenis ini adalah urutan-1, yang bukan "lebih tinggi".*
itu adalah urutan ke-1, tetapi bagaimanapun Monad memiliki jenis(* => *) => *
. Juga, Anda akan mencatat bahwa saya menetapkan "proyeksi yang tepatEither[A, B]
" - implementasinya sepele (tetapi latihan yang baik jika Anda belum melakukannya sebelumnya!)*=>*
lebih tinggi dibenarkan oleh analogi bahwa kita tidak memanggil fungsi biasa (yang memetakan fungsi untuk fungsi non, dengan kata lain, nilai polos ke nilai polos) fungsi urutan lebih tinggi.Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
Manfaatnya persis sama dengan yang diberikan oleh fungsi anonim.
Contoh penggunaan, dengan Scalaz 7. Kami ingin menggunakan
Functor
yang dapat memetakan fungsi pada elemen kedua di aTuple2
.Scalaz menyediakan beberapa konversi implisit yang dapat menyimpulkan argumen tipe
Functor
, jadi kami sering menghindari penulisan ini sama sekali. Baris sebelumnya dapat ditulis ulang sebagai:Jika Anda menggunakan IntelliJ, Anda dapat mengaktifkan Pengaturan, Gaya Kode, Scala, Lipat, Jenis Lambdas. Ini kemudian menyembunyikan bagian crufty sintaks , dan menyajikan yang lebih enak:
Versi Scala yang akan datang mungkin secara langsung mendukung sintaksis seperti itu.
sumber
(1, 2).map(a => a + 1)
REPL: `<console>: 11: error: value map bukan anggota dari (Int, Int) (1, 2) .map (a => a + 1) ^`Untuk meletakkan segala sesuatu dalam konteks: Jawaban ini awalnya diposting di utas lain. Anda melihatnya di sini karena kedua utas telah digabungkan. Pernyataan pertanyaan di utas tersebut adalah sebagai berikut:
Menjawab:
Yang menggarisbawahi dalam kotak setelah
P
menyiratkan bahwa itu adalah konstruktor tipe mengambil satu jenis dan mengembalikan jenis lainnya. Contoh konstruktor tipe dengan jenis ini:List
,Option
.Memberikan
List
sebuahInt
, jenis beton, dan memberikan AndaList[Int]
, jenis beton yang lain. BerikanList
sebuahString
dan memberikan AndaList[String]
. DllJadi,
List
,Option
dapat dianggap fungsi tingkat jenis arity 1. Secara formal kita katakan, mereka memiliki sejenis* -> *
. Tanda bintang menunjukkan suatu tipe.Sekarang
Tuple2[_, _]
adalah konstruktor tipe dengan jenis(*, *) -> *
yaitu Anda harus memberikannya dua jenis untuk mendapatkan tipe baru.Sejak tanda tangan mereka tidak cocok, Anda tidak dapat menggantikan
Tuple2
untukP
. Yang perlu Anda lakukan adalah menerapkan sebagianTuple2
pada salah satu argumennya, yang akan memberi kami konstruktor tipe dengan jenis* -> *
, dan kami dapat menggantinyaP
.Sayangnya Scala tidak memiliki sintaks khusus untuk aplikasi parsial konstruktor tipe, dan karenanya kita harus menggunakan monstrositas yang disebut tipe lambdas. (Apa yang Anda miliki dalam contoh Anda.) Mereka disebut itu karena mereka analog dengan ekspresi lambda yang ada di tingkat nilai.
Contoh berikut mungkin membantu:
Edit:
Level nilai lebih banyak dan pararel level tipe.
Dalam kasus yang telah Anda sajikan, parameter type
R
adalah fungsi lokalTuple2Pure
sehingga Anda tidak dapat dengan mudah mendefinisikannyatype PartialTuple2[A] = Tuple2[R, A]
, karena tidak ada tempat di mana Anda dapat menempatkan sinonim tersebut.Untuk menangani kasus seperti itu, saya menggunakan trik berikut yang menggunakan anggota tipe. (Semoga contohnya cukup jelas.)
sumber
type World[M[_]] = M[Int]
menyebabkan bahwa apa pun yang kita masukkan ke dalamA
diX[A]
dalamimplicitly[X[A] =:= Foo[String,Int]]
selalu benar saya menebak.sumber