Một loại cao hơn trong Scala là gì?


275

Bạn có thể tìm thấy những điều sau đây trên web:

  1. Loại cao hơn == loại xây dựng?

    class AClass[T]{...} // For example, class List[T]

    Một số người nói rằng đây là loại loại cao hơn, bởi vì nó trừu tượng hơn các loại sẽ tuân thủ định nghĩa.

    Các loại cao hơn là các loại lấy các loại khác và xây dựng một loại mới

    Chúng mặc dù còn được gọi là xây dựng kiểu . (Ví dụ, trong Lập trình trong Scala ).

  2. Kiểu xây dựng loại cao hơn == type constructor mà lấy constructor kiểu làm tham số kiểu?

    Trong bài báo Generics of a Kind Kind , bạn có thể đọc

    ... các loại trừu tượng trên các loại trừu tượng hơn các loại ('loại cao hơn') ... "

    điều đó cho thấy rằng

    class XClass[M[T]]{...} // or
    
    trait YTrait[N[_]]{...} // e.g. trait Functor[F[_]]

    là một loại cao hơn.

Vì vậy, với điều này trong tâm trí, rất khó để phân biệt giữa các loại nhà xây dựng , loại kinded cao hơnloại nhà xây dựng trong đó có nhà xây dựng kiểu như tham số kiểu , vì vậy câu hỏi ở trên.


Đã thêm Functor của Landei làm ví dụ.
Lutz

Câu trả lời:


284

Hãy để tôi bù đắp cho việc bắt đầu một số sự nhầm lẫn này bằng cách đưa ra một số định hướng. Tôi thích sử dụng sự tương tự với mức giá trị để giải thích điều này, vì mọi người có xu hướng quen thuộc hơn với nó.

Hàm tạo kiểu là một kiểu mà bạn có thể áp dụng để gõ đối số để "xây dựng" một kiểu.

Hàm tạo giá trị là một giá trị mà bạn có thể áp dụng cho các đối số giá trị để "xây dựng" một giá trị.

Các hàm tạo giá trị thường được gọi là "hàm" hoặc "phương thức". Các "nhà xây dựng" này cũng được gọi là "đa hình" (bởi vì chúng có thể được sử dụng để xây dựng "công cụ" có "hình dạng" khác nhau) hoặc "trừu tượng" (vì chúng trừu tượng hóa những gì khác nhau giữa các thời điểm đa hình khác nhau).

Trong bối cảnh trừu tượng / đa hình, thứ tự đầu tiên đề cập đến "sử dụng một lần" trừu tượng: bạn trừu tượng hóa một loại một lần, nhưng bản thân loại đó không thể trừu tượng hơn bất cứ điều gì. Java 5 generic là thứ nhất.

Giải thích theo thứ tự đầu tiên về các đặc tính trừu tượng ở trên là:

Hàm tạo kiểu là một kiểu mà bạn có thể áp dụng cho các đối số kiểu thích hợp để "xây dựng" một kiểu thích hợp.

Hàm tạo giá trị là một giá trị mà bạn có thể áp dụng cho các đối số giá trị phù hợp để "xây dựng" một giá trị phù hợp.

Để nhấn mạnh không có trừu tượng liên quan (tôi đoán bạn có thể gọi đây là "zero-trật tự", nhưng tôi đã không nhìn thấy bất cứ nơi nào sử dụng này), chẳng hạn như giá trị 1hoặc các loại String, chúng ta thường nói điều gì đó là một giá trị "đúng" hoặc gõ.

Một giá trị phù hợp là "có thể sử dụng ngay lập tức" theo nghĩa là nó không chờ đợi các đối số (nó không trừu tượng đối với chúng). Hãy nghĩ về chúng như các giá trị mà bạn có thể dễ dàng in / kiểm tra (tuần tự hóa một chức năng là gian lận!).

Một kiểu thích hợp là một kiểu phân loại các giá trị (bao gồm các hàm tạo giá trị), các hàm tạo kiểu không phân loại bất kỳ giá trị nào (trước tiên chúng cần được áp dụng cho các đối số kiểu đúng để mang lại một kiểu thích hợp). Để khởi tạo một loại, nó là cần thiết (nhưng không đủ) rằng đó là một loại thích hợp. (Nó có thể là một lớp trừu tượng hoặc một lớp mà bạn không có quyền truy cập.)

"Thứ tự cao hơn" chỉ đơn giản là một thuật ngữ chung có nghĩa là sử dụng lặp lại đa hình / trừu tượng. Nó có nghĩa là điều tương tự cho các loại và giá trị đa hình. Cụ thể, một sự trừu tượng hóa bậc cao trừu tượng hơn một cái gì đó trừu tượng hóa trên một cái gì đó. Đối với các loại, thuật ngữ "loại cao hơn" là phiên bản có mục đích đặc biệt của "thứ tự cao hơn" tổng quát hơn.

Do đó, phiên bản bậc cao của đặc tính của chúng tôi trở thành:

Hàm tạo kiểu là một kiểu mà bạn có thể áp dụng cho kiểu đối số (kiểu đúng hoặc hàm tạo kiểu) để "xây dựng" một kiểu thích hợp (hàm tạo).

Hàm tạo giá trị là một giá trị mà bạn có thể áp dụng cho các đối số giá trị (giá trị phù hợp hoặc hàm tạo giá trị) để "xây dựng" một giá trị phù hợp (hàm tạo).

Do đó, "bậc cao" đơn giản có nghĩa là khi bạn nói "trừu tượng hóa X", bạn thực sự có ý đó! Cái Xđược trừu tượng hóa không làm mất "quyền trừu tượng" của chính nó: nó có thể trừu tượng hóa tất cả những gì nó muốn. (Nhân tiện, tôi sử dụng động từ "trừu tượng" ở đây có nghĩa là: bỏ đi một cái gì đó không cần thiết cho định nghĩa của một giá trị hoặc loại, để nó có thể được thay đổi / cung cấp bởi người sử dụng trừu tượng làm đối số .)

Dưới đây là một số ví dụ (lấy cảm hứng từ các câu hỏi của Lutz qua email) về các giá trị và loại phù hợp, thứ nhất và thứ tự cao hơn:

                   proper    first-order           higher-order

values             10        (x: Int) => x         (f: (Int => Int)) => f(10)
types (classes)    String    List                  Functor
types              String    ({type λ[x] = x})#λ   ({type λ[F[x]] = F[String]})#λ

Trong đó các lớp được sử dụng được định nghĩa là:

class String
class List[T]
class Functor[F[_]]

Để tránh sự gián tiếp thông qua việc xác định các lớp, bạn cần thể hiện bằng cách nào đó biểu thị các hàm loại ẩn danh, không thể biểu thị trực tiếp trong Scala, nhưng bạn có thể sử dụng các kiểu cấu trúc mà không cần quá nhiều cú pháp ( kiểu này là do https://stackoverflow.com / users / 160378 / retronym afaik):

Trong một số phiên bản giả định trong tương lai của Scala hỗ trợ các hàm loại ẩn danh, bạn có thể rút ngắn dòng cuối cùng từ các ví dụ thành:

types (informally) String    [x] => x              [F[x]] => F[String]) // I repeat, this is not valid Scala, and might never be

(Trên một ghi chú cá nhân, tôi rất tiếc đã từng nói về "các loại loại cao hơn", tất cả chúng chỉ là các loại! Khi bạn thực sự cần phải phân tán, tôi khuyên bạn nên nói những thứ như "tham số hàm tạo kiểu", "thành viên hàm tạo kiểu" hoặc "bí danh nhà xây dựng kiểu", để nhấn mạnh rằng bạn không nói về chỉ các loại thích hợp.)

ps: Để làm phức tạp vấn đề hơn nữa, "đa hình" không rõ ràng theo một cách khác, vì một loại đa hình đôi khi có nghĩa là một loại được định lượng phổ biến, như Forall T, T => T, là một loại thích hợp, vì nó phân loại các giá trị đa hình (trong Scala, giá trị này có thể được phân loại được viết dưới dạng cấu trúc {def apply[T](x: T): T = x})



5
Bài viết "Đa hình về nhà xây dựng kiểu" của Adriaan tại adriaanm.github.com/research/2010/10/06/ ám
Steven Shaw

Tôi tiếp tục đọc nó như một sự tử tế cao hơn và tưởng tượng một tinh thần tốt
bụng

110

(Câu trả lời này là một nỗ lực để trang trí câu trả lời của Adriaan Moors bằng một số thông tin về đồ họa và lịch sử.)

Các loại cao hơn là một phần của Scala kể từ 2.5.

  • Trước đó Scala, như Java cho đến nay, không cho phép sử dụng hàm tạo kiểu ("generic" trong Java) để được sử dụng làm tham số kiểu cho hàm tạo kiểu. ví dụ

     trait Monad [M[_]]

    là không thể

    Trong Scala 2.5, hệ thống loại đã được mở rộng nhờ khả năng phân loại các loại ở mức cao hơn (được gọi là đa hình xây dựng kiểu ). Những phân loại này được gọi là các loại.

    Kiểu và loại thực tế, ** có nguồn gốc ** từ "Generics of a Kind Kind" (Ảnh có nguồn gốc từ Generics of a Better Kind )

    Hậu quả là, hàm tạo kiểu đó (vd List) có thể được sử dụng giống như các kiểu khác trong vị trí tham số kiểu của các hàm tạo kiểu và vì vậy chúng trở thành các kiểu lớp đầu tiên kể từ Scala 2.5. (Tương tự như các hàm là giá trị hạng nhất trong Scala).

    Trong ngữ cảnh của một hệ thống loại hỗ trợ các loại cao hơn, chúng ta có thể phân biệt các loại thích hợp , loại như Inthoặc List[Int]từ loại thứ nhất như Listloại loại cao hơn như FunctorhoặcMonad (các loại trừu tượng hơn các loại trừu tượng hơn các loại).

    Hệ thống loại Java ở phía bên kia không hỗ trợ các loại và do đó không có loại "loại cao hơn".

    Vì vậy, điều này phải được nhìn thấy dựa trên nền tảng của hệ thống loại hỗ trợ.

  • Trong trường hợp của Scala, bạn thường thấy các ví dụ về một hàm tạo kiểu như

     trait Iterable[A, Container[_]]

    với tiêu đề "Các loại được phân loại cao hơn", ví dụ như trong Scala dành cho các lập trình viên chung, phần 4.3

    Điều này đôi khi sai lệch, bởi vì nhiều người gọi Containerlà loại cao hơn và không Iterable, nhưng chính xác hơn là,

    việc sử dụng Containernhư tham số hàm tạo kiểu của loại cao hơn (thứ tự cao hơn) ở đây Iterable.


80

Các loại thông thường như IntChar, có phiên bản là giá trị, là *. Các loại xây dựng kiểu unary như Maybe* -> *; các nhà xây dựng kiểu nhị phân như Eithercó loại ( curried ) * -> * -> *, v.v. Bạn có thể xem các loại như MaybeEithernhư các hàm cấp loại: chúng lấy một hoặc nhiều loại và trả về một loại.

Hàm có thứ tự cao hơn nếu nó có thứ tự lớn hơn 1, trong đó thứ tự (không chính thức) độ sâu lồng, bên trái, của mũi tên hàm:

  • Đặt hàng 0: 1 :: Int
  • Đặt hàng 1: chr :: Int -> Char
  • Đơn hàng 2 : fix :: (a -> a) -> a,map :: (a -> b) -> [a] -> [b]
  • Đơn hàng 3: ((A -> B) -> C) -> D
  • Đơn hàng 4: (((A -> B) -> C) -> D) -> E

Vì vậy, câu chuyện dài ngắn, một loại cao hơn chỉ là một hàm bậc cao hơn cấp độ loại.

  • Đặt hàng 0: Int :: *
  • Đặt hàng 1: Maybe :: * -> *
  • Thứ tự 2: Functor :: (* -> *) -> ConstraintLoạihigher-type: chuyển đổi các hàm tạo kiểu đơn nguyên thành các ràng buộc kiểu chữ

Ok tôi hiểu rồi, cái gì sẽ là một ví dụ trong Scala cho (* ⇒ *) ⇒ * và (* ⇒ *) ⇒ (* ⇒ *)? Functor của Landei sẽ phù hợp với loại thứ nhất hay đúng hơn là thứ hai?
Lutz

1
@lutz: Nó sẽ nằm trong danh mục đầu tiên: Functortạo ra một loại thích hợp (tốt, đặc điểm, nhưng cùng một ý tưởng) Functor[F[_]]từ một nhà xây dựng loại F.
Jon Purdy

1
@Jon: Một bài viết rất sâu sắc, cảm ơn bạn. Bộ chuyển đổi loại (* => *) => (* => *)có thể được thể hiện trong Scala? Nếu không, trong ngôn ngữ nào khác?
Eugen Labun

@JonPurdy Việc so sánh giữa * ⇒ * ⇒ *với cà ri rất hữu ích. Cảm ơn!
Lifu Huang

(* ⇒ *) ⇒ (* ⇒ *)cũng có thể được đánh vần (* ⇒ *) ⇒ * ⇒ *. Nó có thể được thể hiện trong Scala như thế nào Foo[F[_], T]. Đây là loại một loại tương tự (trong Haskell) newtype Twice f a = Twice (f (f a))(ví dụ, Twice Maybe IntMaybe (Maybe Int), Twice [] Char[[Char]]) hoặc một cái gì đó thú vị hơn như đơn nguyên miễn phí data Free f a = Pure a | Free (f (Free f a)).
Jon Purdy

37

Tôi muốn nói: Một loại trừu tượng loại cao hơn so với một hàm tạo kiểu. Ví dụ

trait Functor [F[_]] {
   def map[A,B] (fn: A=>B)(fa: F[A]): F[B]
}

Đây Functorlà "loại cao hơn" (như được sử dụng trong bài báo "Generics of a Better Kind" ). Nó không phải là một hàm tạo kiểu cụ thể ("bậc nhất") như List(chỉ tóm tắt các kiểu thích hợp). Nó trừu tượng hóa trên tất cả các hàm tạo kiểu unary ("bậc nhất") (như được biểu thị bằng F[_]).

Hoặc để đặt nó theo một cách khác: Trong Java, chúng tôi có các hàm tạo rõ ràng (ví dụ List<T>), nhưng chúng tôi không có "các loại loại cao hơn", bởi vì chúng tôi không thể trừu tượng hóa chúng (ví dụ: chúng tôi không thể viết Functorgiao diện được xác định ở trên - ít nhất là không trực tiếp ).

Thuật ngữ "đa hình bậc cao (kiểu xây dựng)" được sử dụng để mô tả các hệ thống hỗ trợ "các loại được phân loại cao hơn".


Đây là những gì tôi nghĩ, nhưng nó dường như mâu thuẫn với câu trả lời của Jon, trong đó một "nhà xây dựng kiểu cụ thể" đã là một "loại cao hơn".
Lutz

3
Đúng. Theo câu trả lời của Jon (theo tôi hiểu) List<T>trong Java sẽ là một hàm tạo kiểu đơn nguyên vì nó rõ ràng là loại * -> *. Điều còn thiếu trong câu trả lời của Jon là bạn phải có khả năng trừu tượng hóa "toàn bộ" (và không chỉ qua phần hai *như trong Java) để gọi nó là loại có loại cao hơn.
Landei

@Landai: Các giấy Scala cho các lập trình chung trong phần 4.3 cho thấy các đặc điểm Iterable [A, container [_]] là một loại cao kinded (mặc dù nó không phải là rõ ràng nếu Iterator hoặc container có nghĩa là), nơi ở phía bên kia vero690 trong phần 2.3.1 sử dụng thuật ngữ hàm tạo loại cao hơn để đôi khi như (* -> *) -> * (toán tử loại được tham số hóa với hàm tạo loại bậc cao) trông giống với Iterator hoặc đặc điểm Functor của bạn.
Lutz

1
Điều này có lẽ đúng, nhưng tôi nghĩ chúng ta đang bắt đầu chia tóc ở đây. Điểm quan trọng liên quan đến các loại được phân loại cao hơn là không chỉ các nhà xây dựng kiểu tham gia (đa hình của một nhà xây dựng loại), mà chúng ta có thể trừu tượng hóa các loại cụ thể của các nhà xây dựng loại đó (đa hình của nhà xây dựng loại cao hơn). Chúng tôi có khả năng trừu tượng hóa bất cứ điều gì chúng tôi muốn mà không bị hạn chế (liên quan đến các kiểu và các hàm tạo kiểu), điều này khiến cho việc đặt tên cho tất cả các phiên bản có thể của tính năng đó trở nên ít thú vị hơn. Và nó làm tổn thương não của tôi.
Landei

2
Nói chung, điều quan trọng là phải phân biệt các định nghĩa và tài liệu tham khảo ở đây. Định nghĩa def succ(x: Int) = x+1giới thiệu "hàm tạo giá trị" (xem câu trả lời khác của tôi để biết ý của tôi về điều này) succ(không ai sẽ gọi giá trị này là succ (x: Int)). Bằng cách tương tự, Functorlà loại (thực sự cao hơn) được xác định trong câu trả lời của bạn. Một lần nữa, bạn không nên gọi nó là Functor[F[_]](cái gì F? Cái gì _? Chúng không nằm trong phạm vi! Thật không may, đường cú pháp cho sự tồn tại làm vẩn đục vùng biển ở đây bằng cách F[_]viết tắt F[T forSome {type T}])
Adriaan Moors

0

Scala REPL cung cấp :kindlệnh mà

scala> :help kind

:kind [-v] <type>
Displays the kind of a given type.

Ví dụ,

scala> trait Foo[A]
trait Foo

scala> trait Bar[F[_]]
trait Bar

scala> :kind -v Foo
Foo's kind is F[A]
* -> *
This is a type constructor: a 1st-order-kinded type.

scala> :kind -v Foo[Int]
Foo[Int]'s kind is A
*
This is a proper type.

scala> :kind -v Bar
Bar's kind is X[F[A]]
(* -> *) -> *
This is a type constructor that takes type constructor(s): a higher-kinded type.

scala> :kind -v Bar[Foo]
Bar[Foo]'s kind is A
*
This is a proper type.

Các :helpđịnh nghĩa cung cấp rõ ràng vì vậy tôi nghĩ rằng nó đáng để đăng nó ở đây toàn bộ (Scala 2.13.2)

scala> :help kind

:kind [-v] <type>
Displays the kind of a given type.

    -v      Displays verbose info.

"Kind" is a word used to classify types and type constructors
according to their level of abstractness.

Concrete, fully specified types such as `Int` and `Option[Int]`
are called "proper types" and denoted as `A` using Scala
notation, or with the `*` symbol.

    scala> :kind Option[Int]
    Option[Int]'s kind is A

In the above, `Option` is an example of a first-order type
constructor, which is denoted as `F[A]` using Scala notation, or
* -> * using the star notation. `:kind` also includes variance
information in its output, so if we ask for the kind of `Option`,
we actually see `F[+A]`:

    scala> :k -v Option
    Option's kind is F[+A]
    * -(+)-> *
    This is a type constructor: a 1st-order-kinded type.

When you have more complicated types, `:kind` can be used to find
out what you need to pass in.

    scala> trait ~>[-F1[_], +F2[_]] {}
    scala> :kind ~>
    ~>'s kind is X[-F1[A1],+F2[A2]]

This shows that `~>` accepts something of `F[A]` kind, such as
`List` or `Vector`. It's an example of a type constructor that
abstracts over type constructors, also known as a higher-order
type constructor or a higher-kinded type.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.