Щоб поставити речі в контекст: Ця відповідь спочатку була розміщена в іншій темі. Ви бачите його тут, тому що дві нитки були об'єднані. Постановка запитання у зазначеній темі була такою:
Як вирішити таке визначення типу: 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означає, що конструктор типу приймає один тип і повертає інший тип. Приклади конструкторів типу такого типу: List, Option.
Дайте , тип бетону, і це дає вам , ще один конкретний тип. Дайте і це дає вам . І т.д.ListIntList[Int]ListStringList[String]
Таким чином, List, Optionможна розглядати як функції рівня типу арності 1. Формально ми говоримо, що вони мають вигляд * -> *. Зірочка позначає тип.
Тепер Tuple2[_, _]це конструктор типу з родом, (*, *) -> *тобто вам потрібно дати йому два типи, щоб отримати новий тип.
Так як їх підпис не збігається, ви не можете замінити Tuple2на P. Що вам потрібно зробити, це частково застосувати 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)]