Щоб поставити речі в контекст: Ця відповідь спочатку була розміщена в іншій темі. Ви бачите його тут, тому що дві нитки були об'єднані. Постановка запитання у зазначеній темі була такою:
Як вирішити таке визначення типу: 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
.
Дайте , тип бетону, і це дає вам , ще один конкретний тип. Дайте і це дає вам . І т.д.List
Int
List[Int]
List
String
List[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)]