Apa jenis lambda di Scala dan apa manfaatnya?


152

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 Atanpa 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:


148

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:

trait Monad[M[_]] {
  def point[A](a: A): M[A]
  def bind[A, B](m: M[A])(f: A => M[B]): M[B]
}

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:

class EitherMonad[A] extends Monad[({type λ[α] = Either[A, α]})#λ] {
  def point[B](b: B): Either[A, B]
  def bind[B, C](m: Either[A, B])(f: B => Either[A, C]): Either[A, C]
}

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:

// types X and E are defined in an enclosing scope
private[iteratee] class FG[F[_[_], _], G[_]] {
  type FGA[A] = F[G, A]
  type IterateeM[A] = IterateeT[X, E, FGA, A] 
}

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.


3
Menarik untuk dicatat bahwa Haskell tidak secara langsung mendukung lambda jenis-tingkat , meskipun beberapa peretasan jenis baru (misalnya perpustakaan TypeCompose) memiliki cara untuk menyiasati itu.
Dan Burton

1
Saya ingin tahu melihat Anda menentukan bindmetode untuk EitherMonadkelas Anda . :-) Selain itu, jika saya dapat menyalurkan Adriaan sebentar di sini, Anda tidak menggunakan jenis yang lebih tinggi dalam contoh itu. Anda berada di FG, tetapi tidak di EitherMonad. Sebaliknya, Anda menggunakan konstruktor tipe , yang memiliki jenis * => *. Jenis ini adalah urutan-1, yang bukan "lebih tinggi".
Daniel Spiewak

2
Saya pikir jenis *itu adalah urutan ke-1, tetapi bagaimanapun Monad memiliki jenis (* => *) => *. Juga, Anda akan mencatat bahwa saya menetapkan "proyeksi yang tepat Either[A, B]" - implementasinya sepele (tetapi latihan yang baik jika Anda belum melakukannya sebelumnya!)
Kris Nuttycombe

Saya pikir poin Daniel yang tidak menelepon *=>*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.
jhegedus

1
Buku TAPL Pierce, halaman 442:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus

52

Manfaatnya persis sama dengan yang diberikan oleh fungsi anonim.

def inc(a: Int) = a + 1; List(1, 2, 3).map(inc)

List(1, 2, 3).map(a => a + 1)

Contoh penggunaan, dengan Scalaz 7. Kami ingin menggunakan Functoryang dapat memetakan fungsi pada elemen kedua di a Tuple2.

type IntTuple[+A]=(Int, A)
Functor[IntTuple].map((1, 2))(a => a + 1)) // (1, 3)

Functor[({type l[a] = (Int, a)})#l].map((1, 2))(a => a + 1)) // (1, 3)

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:

(1, 2).map(a => a + 1) // (1, 3)

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:

Functor[[a]=(Int, a)].map((1, 2))(a => a + 1)) // (1, 3)

Versi Scala yang akan datang mungkin secara langsung mendukung sintaksis seperti itu.


Cuplikan terakhir itu terlihat sangat bagus. Plugin IntelliJ scala pastinya luar biasa!
AndreasScheinert

1
Terima kasih! Lambda mungkin hilang dalam contoh terakhir. Selain itu, mengapa tuple functors memilih untuk mengubah nilai terakhir? Apakah konvensi / standar praktis?
Ron

1
Saya menjalankan nightlies untuk Nika dan saya tidak memiliki opsi IDEA yang dijelaskan. Yang cukup menarik, ada adalah pemeriksaan untuk "Terapan Jenis Lambda dapat disederhanakan."
Randall Schulz

6
Itu dipindahkan ke Pengaturan -> Editor -> Lipat Kode.
retronym

@retronym, saya mendapat kesalahan saat mencoba (1, 2).map(a => a + 1)REPL: `<console>: 11: error: value map bukan anggota dari (Int, Int) (1, 2) .map (a => a + 1) ^`
Kevin Meredith

41

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:

Bagaimana mengatasi definisi tipe ini: Pure [({type? [A] = (R, a)}) #?]?

Apa alasan menggunakan konstruksi seperti itu?

Dipotong berasal dari perpustakaan scalaz:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

object Pure {
  import Scalaz._
//...
  implicit def Tuple2Pure[R: Zero]: Pure[({type ?[a]=(R, a)})#?] = new Pure[({type ?[a]=(R, a)})#?] {
  def pure[A](a: => A) = (Ø, a)
  }

//...
}

Menjawab:

trait Pure[P[_]] {
  def pure[A](a: => A): P[A]
}

Yang menggarisbawahi dalam kotak setelah Pmenyiratkan bahwa itu adalah konstruktor tipe mengambil satu jenis dan mengembalikan jenis lainnya. Contoh konstruktor tipe dengan jenis ini: List, Option.

Memberikan Listsebuah Int, jenis beton, dan memberikan Anda List[Int], jenis beton yang lain. Berikan Listsebuah Stringdan memberikan Anda List[String]. Dll

Jadi, List, Optiondapat 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 Tuple2untuk P. Yang perlu Anda lakukan adalah menerapkan sebagian Tuple2 pada salah satu argumennya, yang akan memberi kami konstruktor tipe dengan jenis * -> *, dan kami dapat menggantinya P.

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:

// VALUE LEVEL

// foo has signature: (String, String) => String
scala> def foo(x: String, y: String): String = x + " " + y
foo: (x: String, y: String)String

// world wants a parameter of type String => String    
scala> def world(f: String => String): String = f("world")
world: (f: String => String)String

// So we use a lambda expression that partially applies foo on one parameter
// to yield a value of type String => String
scala> world(x => foo("hello", x))
res0: String = hello world


// TYPE LEVEL

// Foo has a kind (*, *) -> *
scala> type Foo[A, B] = Map[A, B]
defined type alias Foo

// World wants a parameter of kind * -> *
scala> type World[M[_]] = M[Int]
defined type alias World

// So we use a lambda lambda that partially applies Foo on one parameter
// to yield a type of kind * -> *
scala> type X[A] = World[({ type M[A] = Foo[String, A] })#M]
defined type alias X

// Test the equality of two types. (If this compiles, it means they're equal.)
scala> implicitly[X[Int] =:= Foo[String, Int]]
res2: =:=[X[Int],Foo[String,Int]] = <function1>

Edit:

Level nilai lebih banyak dan pararel level tipe.

// VALUE LEVEL

// Instead of a lambda, you can define a named function beforehand...
scala> val g: String => String = x => foo("hello", x)
g: String => String = <function1>

// ...and use it.
scala> world(g)
res3: String = hello world

// TYPE LEVEL

// Same applies at type level too.
scala> type G[A] = Foo[String, A]
defined type alias G

scala> implicitly[X =:= Foo[String, Int]]
res5: =:=[X,Foo[String,Int]] = <function1>

scala> type T = World[G]
defined type alias T

scala> implicitly[T =:= Foo[String, Int]]
res6: =:=[T,Foo[String,Int]] = <function1>

Dalam kasus yang telah Anda sajikan, parameter type Radalah fungsi lokal Tuple2Puresehingga Anda tidak dapat dengan mudah mendefinisikannya type 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.)

scala> type Partial2[F[_, _], A] = {
     |   type Get[B] = F[A, B]
     | }
defined type alias Partial2

scala> implicit def Tuple2Pure[R]: Pure[Partial2[Tuple2, R]#Get] = sys.error("")
Tuple2Pure: [R]=> Pure[[B](R, B)]

0

type World[M[_]] = M[Int]menyebabkan bahwa apa pun yang kita masukkan ke dalam Adi X[A]dalam implicitly[X[A] =:= Foo[String,Int]]selalu benar saya menebak.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.