回答:
種類の高い型を扱う場合、型ラムダは非常に重要です。
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をインスタンス化することはありません。型システムで私が欲しいものを表現するためのハックとして存在しているだけです。
bind
あなたがEitherMonad
クラスのメソッドを定義するのを見てみたいです。:-)それとは別に、ここでAdriaanを少しの間チャネリングする場合、その例では種類の高い型を使用していません。あなたはにいますが、FG
ではありませんEitherMonad
。むしろ、種類のある型コンストラクタを使用しています* => *
。この種類は次数1であり、「上位」ではありません。
*
は1次だと思っていましたが、いずれにしてもモナドには種類があり(* => *) => *
ます。また、「正しい投影法」を指定したことにも気づくでしょうEither[A, B]
-実装は簡単です(ただし、これまでに行ったことがない場合は良い練習です!)
*=>*
高次を呼び出さないという点は、通常の関数(非関数を非関数にマップする、つまり単純な値を単純な値にマップする)の高次関数を呼び出さないというアナロジーによって正当化されると思います。
Type expressions with kinds like (*⇒*)⇒* are called higher-order typeoperators.
利点は、匿名関数によって付与されるものとまったく同じです。
def inc(a: Int) = a + 1; List(1, 2, 3).map(inc)
List(1, 2, 3).map(a => a + 1)
Scalaz 7での使用例。のFunctor
2番目の要素に関数をマップできるを使用したいとします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の将来のバージョンでは、このような構文が直接サポートされる可能性があります。
(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)^`
状況を説明するには:この回答は元々別のスレッドに投稿されていました。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つの型を取り、別の型を返すことを意味します。この種類の型コンストラクタの例:List
、Option
。
付けて、具体的なタイプを、そしてそれはあなたを与える、別の具体的なタイプを。与えると、それはあなたを与えます。等。List
Int
List[Int]
List
String
List[String]
だから、List
、Option
正式に我々は、彼らが種類を持って、言うアリティ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)]