web-dev-qa-db-ja.com

Scalaのタイプラムダとは何ですか?また、その利点は何ですか?

いつか半神秘的な記法に出くわす

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

in Scala=ブログ投稿。これにより、「そのタイプラムダトリックを使用した」ハンドウェーブが得られます。

私はこれについていくつかの意図を持っていますが(定義を汚染することなく匿名型パラメータAを取得しますか?)、型ラムダのトリックとは何か、そしてその利点は何かを説明する明確なソースは見つかりませんでした。それは単なる構文糖ですか、それとも新しい次元を開きますか?

148
ron

タイプラムダは、より種類の高いタイプを扱う場合に非常に重要です。

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

これは、型システムでのカリー化の例です。EitherMonadのインスタンスを作成するときに、いずれかの型を指定する必要があるように、Etherの型をカリー化しました。もう一方は、ポイントまたはバインドを呼び出すときに提供されます。

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

場合によっては、インラインで記述するのが面倒な、より洗練された型ラムダが必要になる場合があります。今日の私のコードからの例を以下に示します。

// 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をインスタンス化することはありません。型システムで私が望むものを表現できるようにするためのハックとしてそこにあります。

147
Kris Nuttycombe

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

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

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

Scalaz 7での使用例。Tuple2の2番目の要素に関数をマッピングできるFunctorを使用します。

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の将来のバージョンでは、このような構文が直接サポートされる可能性があります。

52
retronym

コンテキストに物事を置くために:この答えはもともと別のスレッドに投稿されました。 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

ListInt(具象型)を与えると、別の具象型List[Int]が得られます。 ListStringを与えると、List[String]が得られます。等。

したがって、ListOptionは、アリティ1の型レベル関数と見なすことができます。正式には、それらは種類* -> *を持っています。アスタリスクはタイプを示します。

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

署名が一致しないため、PTuple2に置き換えることはできません。あなたがする必要があるのは、その引数の1つに部分的に適用Tuple2です。これにより、種類が* -> *の型コンストラクタが得られ、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>

提示した場合、型パラメーター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)]
40
missingfaktor

type World[M[_]] = M[Int]は、Aに入れたものがX[A] the implicitly[X[A] =:= Foo[String,Int]]は常に真実だと思います。

0
wiesiu_p