Câu trả lời:
Tôi nghĩ về nó như thế nào:
type
được sử dụng để xác định các loại liên minh mới:
type Thing = Something | SomethingElse
Trước định nghĩa này Something
và SomethingElse
không có nghĩa gì cả. Bây giờ chúng đều thuộc loại Thing
mà chúng ta vừa định nghĩa.
type alias
được sử dụng để đặt tên cho một số loại khác đã tồn tại:
type alias Location = { lat:Int, long:Int }
{ lat = 5, long = 10 }
có loại { lat:Int, long:Int }
, đã là một loại hợp lệ. Nhưng bây giờ chúng ta cũng có thể nói nó có loại Location
vì đó là bí danh cho cùng loại.
Điều đáng chú ý là phần sau sẽ biên dịch tốt và hiển thị "thing"
. Mặc dù chúng tôi chỉ định thing
là a String
và aliasedStringIdentity
nhận AliasedString
, chúng tôi sẽ không gặp lỗi rằng có một loại không khớp giữa String
/ AliasedString
:
import Graphics.Element exposing (show)
type alias AliasedString = String
aliasedStringIdentity: AliasedString -> AliasedString
aliasedStringIdentity s = s
thing : String
thing = "thing"
main =
show <| aliasedStringIdentity thing
{ lat:Int, long:Int }
không xác định một kiểu mới. Đó là một loại hợp lệ rồi. type alias Location = { lat:Int, long:Int }
cũng không xác định một kiểu mới, nó chỉ đặt một tên khác (có lẽ mô tả hơn) cho một kiểu đã hợp lệ. type Location = Geo { lat:Int, long:Int }
sẽ xác định một loại mới ( Location
)
Điều quan trọng là từ alias
. Trong quá trình lập trình, khi bạn muốn nhóm những thứ thuộc về nhau, bạn đặt nó vào một bản ghi, như trong trường hợp một điểm
{ x = 5, y = 4 }
hoặc một hồ sơ học sinh.
{ name = "Billy Bob", grade = 10, classof = 1998 }
Bây giờ, nếu bạn cần chuyển các bản ghi này xung quanh, bạn phải viết ra toàn bộ loại, như:
add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
{ a.x + b.x, a.y + b.y }
Nếu bạn có thể bí danh một điểm, chữ ký sẽ dễ viết hơn rất nhiều!
type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
{ a.x + b.x, a.y + b.y }
Vì vậy, bí danh là cách viết tắt của một cái gì đó khác. Đây là cách viết tắt của một loại bản ghi. Bạn có thể nghĩ nó giống như việc đặt tên cho loại bản ghi mà bạn sẽ sử dụng thường xuyên. Đó là lý do tại sao nó được gọi là bí danh - đó là một tên khác của loại bản ghi trống được đại diện bởi{ x:Int, y:Int }
Trong khi type
giải quyết một vấn đề khác. Nếu bạn đến từ OOP, đó là vấn đề bạn giải quyết với tính kế thừa, quá tải toán tử, v.v. - đôi khi, bạn muốn coi dữ liệu như một thứ chung chung và đôi khi bạn muốn coi nó như một thứ cụ thể.
Một nơi phổ biến xảy ra là khi chuyển các tin nhắn - như hệ thống bưu điện. Khi bạn gửi một lá thư, bạn muốn hệ thống bưu chính coi tất cả các thư như nhau, vì vậy bạn chỉ phải thiết kế hệ thống bưu chính một lần. Và bên cạnh đó, công việc định tuyến thông báo phải độc lập với thông báo chứa bên trong. Chỉ khi bức thư đến đích, bạn mới quan tâm đến nội dung thông điệp là gì.
Theo cách tương tự, chúng ta có thể định nghĩa a type
là sự kết hợp của tất cả các loại thông báo khác nhau có thể xảy ra. Giả sử chúng tôi đang triển khai một hệ thống nhắn tin giữa sinh viên đại học với cha mẹ của họ. Vì vậy, chỉ có hai tin nhắn mà những đứa trẻ đại học có thể gửi: "Tôi cần tiền bia" và "Tôi cần quần lót".
type MessageHome = NeedBeerMoney | NeedUnderpants
Vì vậy, bây giờ, khi chúng tôi thiết kế hệ thống định tuyến, các loại cho các chức năng của chúng tôi có thể chuyển đi xung quanh MessageHome
, thay vì lo lắng về tất cả các loại thông báo khác nhau. Hệ thống định tuyến không quan tâm. Nó chỉ cần biết nó là một MessageHome
. Chỉ khi tin nhắn đến đích, nhà của cha mẹ, bạn mới cần tìm hiểu nó là gì.
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
Nếu bạn biết kiến trúc Elm, thì hàm cập nhật là một câu lệnh trường hợp khổng lồ, vì đó là đích của nơi thông báo được định tuyến và do đó được xử lý. Và chúng tôi sử dụng các kiểu liên minh để có một kiểu duy nhất để xử lý khi truyền thông báo, nhưng sau đó có thể sử dụng câu lệnh trường hợp để xác định chính xác thông điệp đó là gì, vì vậy chúng tôi có thể xử lý nó.
Hãy để tôi bổ sung cho các câu trả lời trước bằng cách tập trung vào các ca sử dụng và cung cấp một chút ngữ cảnh về các hàm và mô-đun của phương thức khởi tạo.
type alias
Tạo bí danh và hàm tạo cho bản ghi
Đây là trường hợp sử dụng phổ biến nhất: bạn có thể xác định tên thay thế và hàm tạo cho một loại định dạng bản ghi cụ thể.
type alias Person =
{ name : String
, age : Int
}
Việc xác định bí danh kiểu tự động ngụ ý hàm khởi tạo sau (mã giả):
Person : String -> Int -> { name : String, age : Int }
Điều này có thể hữu ích, chẳng hạn khi bạn muốn viết bộ giải mã Json.
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "name" Json.Decode.String)
(Json.Decode.field "age" Int)
Chỉ định các trường bắt buộc
Đôi khi họ gọi nó là "bản ghi có thể mở rộng", có thể gây hiểu nhầm. Cú pháp này có thể được sử dụng để chỉ định rằng bạn đang mong đợi một số bản ghi với các trường cụ thể hiện diện. Nhu la:
type alias NamedThing x =
{ x
| name : String
}
showName : NamedThing x -> Html msg
showName thing =
Html.text thing.name
Sau đó, bạn có thể sử dụng chức năng trên như thế này (ví dụ trong chế độ xem của bạn):
let
joe = { name = "Joe", age = 34 }
in
showName joe
Bài nói chuyện của Richard Feldman trên ElmEurope 2017 có thể cung cấp thêm một số thông tin chi tiết về thời điểm phong cách này đáng sử dụng.
Đổi tên nội dung
Bạn có thể làm điều này, vì những tên mới có thể cung cấp thêm ý nghĩa sau này trong mã của bạn, như trong ví dụ này
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
Có lẽ một ví dụ tốt hơn về kiểu sử dụng này trong lõi làTime
.
Hiển thị lại một loại từ một mô-đun khác
Nếu bạn đang viết một gói (không phải một ứng dụng), bạn có thể cần triển khai một loại trong một mô-đun, có thể là trong một mô-đun nội bộ (không tiếp xúc), nhưng bạn muốn hiển thị loại từ một mô-đun (công khai) khác. Hoặc, cách khác, bạn muốn hiển thị loại của mình từ nhiều mô-đun.
Task
trong lõi và Http.Request trong Http là ví dụ đầu tiên, trong khi Json.Encode.Value và Json.Decode.Value cặp là một ví dụ về sau.
Bạn chỉ có thể làm điều này khi bạn muốn giữ kiểu mờ: bạn không để lộ các hàm khởi tạo. Để biết chi tiết xem cách sử dụng type
bên dưới.
Cần lưu ý rằng trong các ví dụ trên, chỉ # 1 cung cấp một hàm khởi tạo. Nếu bạn để lộ bí danh kiểu của mình trong # 1 như module Data exposing (Person)
vậy sẽ làm lộ tên kiểu cũng như hàm khởi tạo.
type
Xác định kiểu liên minh được gắn thẻ
Đây là trường hợp sử dụng phổ biến nhất, một ví dụ điển hình về nó là Maybe
kiểu trong lõi :
type Maybe a
= Just a
| Nothing
Khi bạn xác định một kiểu, bạn cũng xác định các hàm khởi tạo của nó. Trong trường hợp Có thể đây là (mã giả):
Just : a -> Maybe a
Nothing : Maybe a
Có nghĩa là nếu bạn khai báo giá trị này:
mayHaveANumber : Maybe Int
Bạn có thể tạo nó bằng cách
mayHaveANumber = Nothing
hoặc là
mayHaveANumber = Just 5
Các thẻ Just
và Nothing
không chỉ đóng vai trò là hàm khởi tạo, chúng còn đóng vai trò là hàm hủy hoặc mẫu trong một case
biểu thức. Có nghĩa là sử dụng các mẫu này, bạn có thể thấy bên trong Maybe
:
showValue : Maybe Int -> Html msg
showValue mayHaveANumber =
case mayHaveANumber of
Nothing ->
Html.text "N/A"
Just number ->
Html.text (toString number)
Bạn có thể làm điều này, bởi vì mô-đun Có thể được định nghĩa như
module Maybe exposing
( Maybe(Just,Nothing)
Nó cũng có thể nói
module Maybe exposing
( Maybe(..)
Trong trường hợp này, cả hai tương đương nhau, nhưng rõ ràng được coi là một đức tính tốt trong Elm, đặc biệt là khi bạn đang viết một gói hàng.
Ẩn chi tiết triển khai
Như đã chỉ ra ở trên, đó là một lựa chọn có chủ ý mà các hàm khởi tạo của Maybe
có thể nhìn thấy được đối với các mô-đun khác.
Tuy nhiên, có những trường hợp khác khi tác giả quyết định giấu chúng đi. Một ví dụ về điều này trong cốt lõi làDict
. Là người tiêu dùng gói, bạn sẽ không thể thấy chi tiết triển khai của thuật toán cây Đỏ / Đen đằng sau Dict
và gây rối trực tiếp với các nút. Việc ẩn các hàm khởi tạo buộc người tiêu dùng mô-đun / gói của bạn chỉ tạo các giá trị thuộc loại của bạn (và sau đó chuyển đổi các giá trị đó) thông qua các hàm mà bạn hiển thị.
Đây là lý do tại sao đôi khi những thứ như thế này xuất hiện trong mã
type Person =
Person { name : String, age : Int }
Không giống như type alias
định nghĩa ở đầu bài đăng này, cú pháp này tạo ra một kiểu "union" mới chỉ với một hàm tạo, nhưng hàm tạo đó có thể bị ẩn khỏi các mô-đun / gói khác.
Nếu loại được hiển thị như thế này:
module Data exposing (Person)
Chỉ mã trong Data
mô-đun mới có thể tạo giá trị Người và chỉ mã đó mới có thể khớp với mẫu trên đó.
Sự khác biệt chính, như tôi thấy, là liệu trình kiểm tra kiểu có hét lên với bạn không nếu bạn sử dụng kiểu "synomical".
Tạo tệp sau, đặt nó ở đâu đó và chạy elm-reactor
, sau đó truy cập http://localhost:8000
để xem sự khác biệt:
-- Boilerplate code
module Main exposing (main)
import Html exposing (..)
main =
Html.beginnerProgram
{
model = identity,
view = view,
update = identity
}
-- Our type system
type alias IntRecordAlias = {x : Int}
type IntRecordType =
IntRecordType {x : Int}
inc : {x : Int} -> {x : Int}
inc r = {r | x = .x r + 1}
view model =
let
-- 1. This will work
r : IntRecordAlias
r = {x = 1}
-- 2. However, this won't work
-- r : IntRecordType
-- r = IntRecordType {x = 1}
in
Html.text <| toString <| inc r
Nếu bạn bỏ ghi chú 2.
và bình luận, 1.
bạn sẽ thấy:
The argument to function `inc` is causing a mismatch.
34| inc r
^
Function `inc` is expecting the argument to be:
{ x : Int }
But it is:
IntRecordType
An alias
chỉ là tên ngắn hơn của một số loại khác, tương tự class
trong OOP. Ngày hết hạn:
type alias Point =
{ x : Int
, y : Int
}
Một type
(không có bí danh) sẽ cho phép bạn xác định kiểu riêng của bạn, vì vậy bạn có thể xác định các loại như Int
, String
, ... cho bạn ứng dụng. Đối với exmaple, trong trường hợp phổ biến, nó có thể sử dụng để mô tả trạng thái của ứng dụng:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
Vì vậy, bạn có thể dễ dàng xử lý nó trong view
cây du:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
Tôi nghĩ bạn biết sự khác biệt giữa type
và type alias
.
Nhưng tại sao và làm thế nào để sử dụng type
và type alias
rất quan trọng với elm
ứng dụng, các bạn có thể tham khảo các bài báo từ Josh Clayton