Để đặt mọi thứ trong bối cảnh: Câu trả lời này ban đầu được đăng trong một chủ đề khác. Bạn đang nhìn thấy nó ở đây vì hai chủ đề đã được hợp nhất. Các câu hỏi trong chủ đề nói như sau:
Làm cách nào để giải quyết định nghĩa loại này: Pure [({type? [A] = (R, a)}) #?]?
Những lý do của việc sử dụng xây dựng như vậy là gì?
Snipped đến từ thư viện 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)
}
//...
}
Câu trả lời:
trait Pure[P[_]] {
def pure[A](a: => A): P[A]
}
Một dấu gạch dưới trong các hộp sau khi P
ngụ ý rằng nó là một hàm tạo kiểu lấy một kiểu và trả về một kiểu khác. Ví dụ về các loại nhà xây dựng với loại này: List
, Option
.
Cho List
một Int
, một loại bê tông, và nó cung cấp cho bạn List[Int]
, một loại bê tông khác. Cho List
một String
và nó cung cấp cho bạn List[String]
. Vân vân.
Vì vậy, List
, Option
có thể được coi là chức năng mức loại arity 1. Chính thức chúng ta nói, họ có một loại * -> *
. Dấu hoa thị biểu thị một loại.
Bây giờ Tuple2[_, _]
là một hàm tạo kiểu với loại (*, *) -> *
tức là bạn cần cung cấp cho nó hai loại để có được một kiểu mới.
Kể từ khi chữ ký của họ không phù hợp, bạn không thể thay thế Tuple2
cho P
. Những gì bạn cần làm là áp dụng một phần Tuple2
vào một trong các đối số của nó, nó sẽ cung cấp cho chúng ta một hàm tạo kiểu với loại * -> *
và chúng ta có thể thay thế nó cho P
.
Thật không may, Scala không có cú pháp đặc biệt cho ứng dụng một phần của các hàm tạo kiểu, và vì vậy chúng ta phải dùng đến sự quái dị được gọi là kiểu lambdas. (Những gì bạn có trong ví dụ của mình.) Chúng được gọi là bởi vì chúng tương tự như các biểu thức lambda tồn tại ở mức giá trị.
Ví dụ sau có thể giúp:
// 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>
Biên tập:
Nhiều mức giá trị và tương đương mức độ loại.
// 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>
Trong trường hợp bạn đã trình bày, tham số loại R
là cục bộ để hoạt động Tuple2Pure
và do đó bạn không thể xác định đơn giản type PartialTuple2[A] = Tuple2[R, A]
, vì đơn giản là không có nơi nào bạn có thể đặt từ đồng nghĩa đó.
Để giải quyết trường hợp như vậy, tôi sử dụng thủ thuật sau đây sử dụng các thành viên loại. (Hy vọng ví dụ này là tự giải thích.)
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)]