Scalaの型ラムダとは何ですか?それらの利点は何ですか?


152

時々、私は半神秘的な記法に出くわします

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

Scalaのブログ投稿では、「その型ラムダトリックを使用した」ハンドウェーブを提供しています。

私はこれについていくらか直観的ですが(A定義を汚染することなく匿名の型パラメーターを取得しますか?)、型ラムダトリックとは何か、およびその利点は何かを説明する明確な情報源が見つかりませんでした。それは単に構文上の砂糖ですか、それともいくつかの新しい次元を開きますか?


ご覧ください。
シェルビームーアIII

回答:


148

種類の高い型を扱う場合、型ラムダは非常に重要です。

Either [A、B]の正しい射影に対してモナドを定義する簡単な例を考えてみましょう。モナドの型クラスは次のようになります。

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

どちらも2つの引数の型コンストラクタですが、Monadを実装するには、1つの引数の型コンストラクタを指定する必要があります。これに対する解決策は、ラムダ型を使用することです:

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

これは、型システムでのカリー化の例です。Eitherの型をカリー化したため、EitherMonadのインスタンスを作成する場合は、型の1つを指定する必要があります。もちろん、もう1つは、ポイントまたはバインドを呼び出すときに提供されます。

タイプラムダトリックは、タイプ位置の空のブロックが匿名の構造タイプを作成するという事実を利用します。次に、#構文を使用して型メンバーを取得します。

場合によっては、インラインで書き出すのが面倒な、より洗練された型のラムダが必要になることがあります。これが今日の私のコードの例です:

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

このクラスは排他的に存在するため、FG [F、G] #IterateeMなどの名前を使用して、3番目のモナドに特化した2番目のモナドのトランスフォーマーバージョンに特化したIterateeTモナドのタイプを参照できます。スタックを開始すると、これらの種類の構成が非常に必要になります。もちろん、FGをインスタンス化することはありません。型システムで私が欲しいものを表現するためのハックとして存在しているだけです。


3
興味深いことに、Haskellは型レベルのラムダを直接サポートしていませが、一部のnewtypeハッカー(TypeComposeライブラリなど)は、それを回避する方法を備えています。
Dan Burton

1
bindあなたがEitherMonadクラスのメソッドを定義するのを見てみたいです。:-)それとは別に、ここでAdriaanを少しの間チャネリングする場合、その例では種類の高い型を使用していません。あなたはにいますが、FGではありませんEitherMonad。むしろ、種類のある型コンストラクタを使用しています* => *。この種類は次数1であり、「上位」ではありません。
Daniel Spiewak、2012年

2
種類*は1次だと思っていましたが、いずれにしてもモナドには種類があり(* => *) => *ます。また、「正しい投影法」を指定したことにも気づくでしょうEither[A, B]-実装は簡単です(ただし、これまでに行ったことがない場合は良い練習です!)
Kris Nuttycombe

ダニエルが*=>*高次を呼び出さないという点は、通常の関数(非関数を非関数にマップする、つまり単純な値を単純な値にマップする)の高次関数を呼び出さないというアナロジーによって正当化されると思います。
jhegedus 14

1
ピアースのTAPL本、442ページ:Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
jhegedus 14

52

利点は、匿名関数によって付与されるものとまったく同じです。

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

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

Scalaz 7での使用例。のFunctor2番目の要素に関数をマップできるを使用したいとします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は、型引数をに推測できる暗黙の変換をいくつか提供しているFunctorため、これらを完全に記述することは避けます。前の行は次のように書き換えることができます。

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

IntelliJを使用する場合、設定、コードスタイル、Scala、折りたたみ、タイプラムダを有効にできます。これにより、構文の不適切な部分が非表示になり、口当たりが良くなります。

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

Scalaの将来のバージョンでは、このような構文が直接サポートされる可能性があります。


その最後のスニペットは本当に素敵に見えます。IntelliJ scalaプラグインは確かに素晴らしいです!
AndreasScheinert、2012年

1
ありがとう!最後の例ではラムダが欠落している可能性があります。さて、タプルファンクタが最後の値を変換することを選択したのはなぜですか?それは慣習/実用的なデフォルトですか?
2012年

1
Nikaのナイトリーを実行していますが、説明されているIDEAオプションがありません。興味深いことに、そこにあるの検査「応用型ラムダを簡素化することができるが。」
Randall Schulz

6
[設定]-> [エディター]-> [コードの折りたたみ]に移動します。
レトロネーム2012年

@ retronym、(1, 2).map(a => a + 1)REPLを試行したときにエラーが発生しました: `<console>:11:error:value map is not a member of(Int、Int)(1、2).map(a => a + 1)^`
ケビンメレディス2014年

41

状況を説明するには:この回答は元々別のスレッドに投稿されていました。2つのスレッドがマージされているため、ここに表示されています。上記のスレッドの質問文は次のとおりです。

この型定義を解決する方法:Pure [({type?[a] =(R、a)})#?]?

そのような構造を使用する理由は何ですか?

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

//...
}

回答:

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

後のボックス内の下線Pは、型コンストラクターが1つの型を取り、別の型を返すことを意味します。この種類の型コンストラクタの例:ListOption

付けて、具体的なタイプを、そしてそれはあなたを与える、別の具体的なタイプを。与えると、それはあなたを与えます。等。ListIntList[Int]ListStringList[String]

だから、ListOption正式に我々は、彼らが種類を持って、言うアリティ1のタイプレベルの機能と考えることができます* -> *。アスタリスクはタイプを示します。

これTuple2[_, _]で、種類を持つ型コンストラクターになり(*, *) -> *ます。つまり、新しい型を取得するには、2つの型を指定する必要があります。

彼らの署名が一致していないので、あなたは代用できませんTuple2ためP。あなたがする必要があるのは、引数の1つに部分的に適用する ことです。これTuple2により、kindを使用した型コンストラクターが提供され* -> *、それをに置き換えることができPます。

残念ながら、Scalaには型コンストラクターの部分的な適用のための特別な構文がないため、型ラムダと呼ばれる巨大なものに頼らなければなりません。(あなたの例にあるもの。)それらは、値レベルで存在するラムダ式に類似しているので、それと呼ばれます。

次の例が役立ちます。

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

編集:

より多くの価値レベルとタイプレベルの類似点。

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

提示した場合、typeパラメータRは機能に対してローカルであり、その同義語を配置できる場所がないため、をTuple2Pure単に定義することはできませんtype PartialTuple2[A] = Tuple2[R, A]

このような場合に対処するには、型のメンバーを利用する次のトリックを使用します。(うまくいけば、この例は自明です。)

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]何でも私たちは入れていることの原因Aには常に真Iの推測です。X[A]implicitly[X[A] =:= Foo[String,Int]]

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.