Scala'da tip lambdalar nelerdir ve faydaları nelerdir?


152

Bazen yarı gizemli gösterime rastlıyorum

def f[T](..) = new T[({type l[A]=SomeType[A,..]})#l] {..} 

Scala blog yayınlarında "biz bu tip lambda numarasını kullandık" el dalını verdik.

Bununla ilgili bazı niyetlerim olsa da ( Atanımı onunla kirletmek zorunda kalmadan anonim bir tür parametresi kazanıyoruz ?), Lambda numarasının ne olduğunu ve faydalarını açıklayan net bir kaynak bulamadım. Sadece sözdizimsel şeker mi yoksa yeni boyutlar mı açıyor?


Yanıtlar:


148

Tip lambdalar, daha yüksek türlerle çalışırken biraz hayati önem taşır.

İkisinden birinin [A, B] doğru projeksiyonu için bir monad tanımlamanın basit bir örneğini düşünün. Monad tip sınıfı şöyle görünür:

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

Şimdi, ya iki argümanın bir tür kurucusudur, ancak Monad'ı uygulamak için, ona bir argümanın bir tür kurucusunu vermeniz gerekir. Bunun çözümü bir tip lambda kullanmaktır:

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]
}

Bu, tür sisteminde bir köri örneğidir - Either türünü körüklediniz, öyle ki EitherMonad'ın bir örneğini oluşturmak istediğinizde, türlerden birini belirtmeniz gerekir; diğeri elbette çağrı noktanız veya bağlantınız sırasında verilir.

Lambda hüner tipi, bir tip pozisyondaki boş bir bloğun anonim bir yapısal tip oluşturduğu gerçeğinden yararlanır. Daha sonra bir tür üye almak için # sözdizimini kullanırız.

Bazı durumlarda, satır içi yazmak için bir acı olan daha sofistike tip lambdalara ihtiyacınız olabilir. İşte bugünkü kodumdan bir örnek:

// 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] 
}

Bu sınıf sadece FG [F, G] #IterateeM gibi bir isim kullanabilir ve üçüncü bir monad için uzmanlaşmış ikinci bir monadın bazı transformatör versiyonuna özel IterateeT monadının tipine atıfta bulunabilirim. Yığmaya başladığınızda, bu tür yapılar çok gerekli hale gelir. Tabii ki hiçbir zaman bir FG başlatmıyorum; sadece tip sistemde ne istediğimi ifade etmeme izin vermek için bir kesmek gibi.


3
Olması ilginçtir Haskell yok değil doğrudan tipi düzeyinde lambdas destekleyen bazı newtype hackery (örn TypeCompose kütüphanesi) etrafında olsun tür yollarını olmasına rağmen,.
Dan Burton

1
Sınıfınız için bindyöntemi tanımladığınızı görmek isterim EitherMonad. :-) Bunun yanı sıra, burada bir saniye Adriaan'a kanal kurabilirsem, bu örnekte daha yüksek türler kullanmıyorsunuz. İçindesin FG, ama değil EitherMonad. Bunun yerine, türüne sahip tür yapıcıları kullanıyorsunuz * => *. Bu tür "daha yüksek" olmayan düzen-1'dir.
Daniel Spiewak

2
Bu tür bir *düzen-1 diye düşündüm , ama her durumda Monad'ın iyi bir türü var (* => *) => *. Ayrıca, "doğru projeksiyonu" belirlediğimi de not edeceksiniz Either[A, B]- uygulama önemsizdir (ancak daha önce yapmadıysanız iyi bir egzersiz!)
Kris Nuttycombe

Daniel'in *=>*daha yüksek çağırmama noktasının , sıradan bir işlev (işlev dışı işlevleri olmayan, başka bir deyişle, düz değerleri düz değerlerle eşleştiren) yüksek dereceli işlev olarak adlandırmadığımız benzetmesiyle doğrulandığını tahmin ediyorum .
jhegedus

1
Pierce'ın TAPL kitabı, sayfa 442:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus

52

Avantajlar, anonim işlevlerin sağladığı faydalarla tamamen aynıdır.

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

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

Scalaz 7 ile örnek bir kullanım. FunctorBir fonksiyonu, 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, tür argümanını çıkartabilecek bazı örtük dönüşümler sağlar Functor, bu nedenle bunları genellikle birlikte yazmaktan kaçınırız. Önceki satır şu şekilde yeniden yazılabilir:

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

IntelliJ kullanıyorsanız Ayarlar, Kod Stili, Scala, Katlama, Lambda Tipi'ni etkinleştirebilirsiniz. Bu , sözdiziminin kaba kısımlarını gizler ve daha lezzetli olur:

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

Scala'nın gelecekteki bir sürümü böyle bir sözdizimini doğrudan destekleyebilir.


Son pasaj çok hoş görünüyor. IntelliJ scala eklentisi kesinlikle harika!
AndreasScheinert

1
Teşekkürler! Son örnekte lambda eksik olabilir. Bir kenara, grup functorları neden son değeri dönüştürmeyi seçti? Sözleşme / pratik bir temerrüt mü?
ron

1
Nika için gece koşuyorum ve açıklanan IDEA seçeneğim yok. İlginçtir ki, orada olduğu için bir inceleme "Uygulamalı Tipi Lambda basitleştirilmiş olabilir."
Randall Schulz

6
Ayarlar -> Editör -> Kod Katlama'ya taşınır.
Retronym

@retronym, REPL'de denerken bir hatayla karşılaştım (1, 2).map(a => a + 1): `<console>: 11: error: değer haritası (Int, Int) (1, 2) .map (a => a + 1) ^ '' nin üyesi değil
Kevin Meredith

41

Bir şeyleri bağlama koymak için: Bu cevap başlangıçta başka bir konuya gönderildi. Burada görüyorsunuz çünkü iki konu birleştirildi. Söz konusu evrede yer alan soru ifadesi aşağıdaki gibidir:

Bu tür tanımı nasıl çözebilirim: Pure [({type? [A] = (R, a)}) #?]?

Böyle bir yapıyı kullanmanın nedenleri nelerdir?

Snipped scalaz kütüphanesinden geliyor:

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)
  }

//...
}

Cevap:

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

Sonrasında kutulardaki bir alt çizgi P, bir tür yapıcısının bir türü aldığını ve başka bir türü döndürdüğünü gösterir. Bu tür tip yapıcılara örnekler: List, Option.

Ver Listbir Int, bir beton türü ve size senkronizasyon List[Int], başka somut türü. Ver Listbir Stringve size senkronizasyon List[String]. Vb.

Yani, List, Option1. Resmen dediğimiz Arity tipi seviyede fonksiyonlar olarak kabul edilebilir, bunlar bir tür * -> *. Yıldız işareti bir türü belirtir.

Şimdi Tuple2[_, _]tür ile bir tür yapıcı (*, *) -> *yani yeni bir tür elde etmek için iki tür vermek gerekir.

İmzaları eşleşmediği Tuple2için bunun yerini alamazsınız P. Yapmanız gereken , argümanlarından birine kısmen uygulanır Tuple2 , bu da bize nazik bir tür yapıcısı verir * -> *ve yerine koyabiliriz P.

Ne yazık ki Scala, tip kurucularının kısmi uygulaması için özel bir sözdizimine sahip değildir ve bu nedenle tip lambda denilen canavarlığa başvurmak zorundayız. (Örneğinizde ne var.) Buna değer denilen lambda ifadelerine benzedikleri için denir.

Aşağıdaki örnek yardımcı olabilir:

// 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>

Düzenle:

Daha fazla değer seviyesi ve tip seviyesi paralelliği.

// 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>

Sunmanız durumunda, type parametresinin Rçalışması için yereldir Tuple2Pureve bu nedenle basitçe tanımlayamazsınız type PartialTuple2[A] = Tuple2[R, A], çünkü bu eşanlamlıyı koyabileceğiniz bir yer yoktur.

Böyle bir durumla başa çıkmak için, tip üyelerini kullanan aşağıdaki hileyi kullanıyorum. (Umarım örnek kendini açıklayıcıdır.)

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]biz koymak ne olursa olsun neden Ade her zaman doğru ben tahmin.X[A]implicitly[X[A] =:= Foo[String,Int]]

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.