Sự khác biệt trong Elm giữa bí danh kiểu và kiểu?


93

Trong Elm, tôi không thể tìm ra khi typelà so với từ khóa thích hợp type alias. Tài liệu này dường như không có giải thích về điều này, tôi cũng không thể tìm thấy một tài liệu trong ghi chú phát hành. Điều này có được ghi ở đâu đó không?

Câu trả lời:


136

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 SomethingSomethingElsekhông có nghĩa gì cả. Bây giờ chúng đều thuộc loại Thingmà 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 Locationvì đó 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 thinglà a StringaliasedStringIdentitynhậ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

Không chắc chắn về quan điểm của bạn trong đoạn cuối cùng. Bạn đang cố gắng nói rằng họ vẫn là cùng một kiểu cho dù bạn có bí danh như thế nào?
ZHANG Cheng

7
Vâng, chỉ cần chỉ ra rằng trình biên dịch xem xét loại aliased để được giống như bản gốc
robertjlooby

Vì vậy, khi bạn sử dụng {}cú pháp bản ghi, bạn đang xác định một kiểu mới?

2
{ 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)
robertjlooby

1
Khi nào thì nên sử dụng bí danh type so với type? Nhược điểm của việc luôn sử dụng loại là ở đâu?
Richard Haven

8

Đ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 typelà 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ó.


5

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.



Tập quán của type alias

  1. 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)


  2. 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.

  3. Đổ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 .

  4. 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.
    Tasktrong lõi và Http.Request trong Http là ví dụ đầu tiên, trong khi Json.Encode.ValueJson.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 typebê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.



Tập quán của type

  1. 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à Maybekiể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ẻ JustNothingkhô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 casebiể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.


  1. Ẩ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 Maybecó 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 Dictvà 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 Datamô-đ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 đó.


1

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

0

An aliaschỉ là tên ngắn hơn của một số loại khác, tương tự classtrong 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 viewcâ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 typetype alias.

Nhưng tại sao và làm thế nào để sử dụng typetype aliasrấ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

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.