Perbedaan Elm antara tipe dan tipe alias?


93

Di Elm, saya tidak tahu kapan typeadalah vs kata kunci yang sesuai type alias. Dokumentasi tampaknya tidak memiliki penjelasan tentang ini, saya juga tidak dapat menemukannya di catatan rilis. Apakah ini didokumentasikan di suatu tempat?

Jawaban:


136

Bagaimana saya memikirkannya:

type digunakan untuk menentukan jenis serikat baru:

type Thing = Something | SomethingElse

Sebelum definisi ini Somethingdan SomethingElsetidak berarti apa-apa. Sekarang keduanya adalah tipe Thing, yang baru saja kita definisikan.

type alias digunakan untuk memberi nama pada beberapa tipe lain yang sudah ada:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 }memiliki tipe { lat:Int, long:Int }, yang tadinya merupakan tipe yang valid. Tapi sekarang kita juga bisa mengatakan itu memiliki tipe Locationkarena itu adalah alias untuk tipe yang sama.

Perlu dicatat bahwa berikut ini akan mengkompilasi dengan baik dan ditampilkan "thing". Meskipun kami menetapkan thingadalah a Stringdan aliasedStringIdentitymengambil AliasedString, kami tidak akan mendapatkan kesalahan bahwa ada jenis ketidakcocokan antara 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

Tidak yakin dengan maksud Anda di paragraf terakhir. Apakah Anda mencoba mengatakan mereka masih tipe yang sama tidak peduli bagaimana Anda alias?
ZHANG Cheng

7
Ya, hanya menunjukkan bahwa kompilator menganggap tipe alias sama dengan aslinya
robertjlooby

Jadi saat Anda menggunakan {}sintaks record, Anda mendefinisikan tipe baru?

2
{ lat:Int, long:Int }tidak mendefinisikan tipe baru. Itu sudah tipe yang valid. type alias Location = { lat:Int, long:Int }juga tidak mendefinisikan tipe baru, itu hanya memberi nama lain (mungkin lebih deskriptif) untuk tipe yang sudah valid. type Location = Geo { lat:Int, long:Int }akan mendefinisikan tipe baru ( Location)
robertjlooby

1
Kapan seseorang harus menggunakan alias tipe vs. tipe? Di manakah sisi negatif dari selalu menggunakan tipe?
Richard Haven

8

Kuncinya adalah kata alias. Dalam kursus pemrograman, ketika Anda ingin mengelompokkan hal-hal yang menjadi satu, Anda memasukkannya ke dalam sebuah catatan, seperti dalam kasus sebuah poin

{ x = 5, y = 4 }  

atau catatan siswa.

{ name = "Billy Bob", grade = 10, classof = 1998 }

Sekarang, jika Anda perlu menyebarkan record ini, Anda harus mengeja seluruh tipe, seperti:

add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
  { a.x + b.x, a.y + b.y }

Jika Anda bisa membuat alias sebuah titik, tanda tangannya akan jauh lebih mudah untuk ditulis!

type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
  { a.x + b.x, a.y + b.y }

Jadi alias adalah singkatan dari sesuatu yang lain. Di sini, ini adalah singkatan dari tipe rekaman. Anda dapat menganggapnya sebagai memberi nama untuk jenis rekaman yang akan sering Anda gunakan. Itulah mengapa ini disebut alias - ini adalah nama lain untuk tipe rekaman telanjang yang diwakili oleh{ x:Int, y:Int }

Padahal typememecahkan masalah yang berbeda. Jika Anda berasal dari OOP, itu adalah masalah yang Anda selesaikan dengan warisan, kelebihan beban operator, dll. - terkadang, Anda ingin memperlakukan data sebagai hal yang umum, dan terkadang Anda ingin memperlakukannya seperti hal tertentu.

Hal yang biasa terjadi adalah saat menyebarkan pesan - seperti sistem pos. Saat Anda mengirim surat, Anda ingin sistem pos memperlakukan semua pesan sebagai hal yang sama, jadi Anda hanya perlu merancang sistem pos sekali. Selain itu, tugas perutean pesan harus independen dari pesan yang terkandung di dalamnya. Hanya ketika surat itu mencapai tujuannya barulah Anda peduli tentang apa pesannya.

Dengan cara yang sama, kita dapat mendefinisikan a typesebagai gabungan dari semua jenis pesan berbeda yang dapat terjadi. Katakanlah kita menerapkan sistem pesan antara mahasiswa kepada orang tua mereka. Jadi hanya ada dua pesan yang dapat dikirim oleh anak-anak perguruan tinggi: 'Saya butuh uang bir' dan 'Saya butuh celana dalam'.

type MessageHome = NeedBeerMoney | NeedUnderpants

Jadi sekarang, ketika kita mendesain sistem perutean, tipe untuk fungsi kita bisa begitu saja MessageHome, alih-alih mengkhawatirkan semua jenis pesan yang berbeda. Sistem perutean tidak peduli. Itu hanya perlu tahu itu a MessageHome. Hanya ketika pesan mencapai tujuannya, rumah orang tua, Anda perlu mencari tahu apa itu.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Jika Anda mengetahui arsitektur Elm, fungsi pembaruan adalah pernyataan kasus raksasa, karena itulah tujuan dari mana pesan dirutekan, dan karenanya diproses. Dan kami menggunakan tipe gabungan untuk memiliki satu tipe yang harus ditangani saat meneruskan pesan, tetapi kemudian dapat menggunakan pernyataan kasus untuk mengetahui pesan apa itu sebenarnya, sehingga kami dapat mengatasinya.


5

Izinkan saya melengkapi jawaban sebelumnya dengan berfokus pada kasus penggunaan dan memberikan sedikit konteks pada fungsi dan modul konstruktor.



Penggunaan type alias

  1. Membuat alias dan fungsi konstruktor untuk rekaman.
    Ini kasus penggunaan yang paling umum: Anda dapat menentukan nama alternatif dan fungsi konstruktor untuk jenis format rekaman tertentu.

    type alias Person =
        { name : String
        , age : Int
        }
    

    Mendefinisikan alias tipe secara otomatis menyiratkan fungsi konstruktor berikut (kode pseudo):
    Person : String -> Int -> { name : String, age : Int }
    Ini bisa berguna, misalnya ketika Anda ingin menulis dekoder Json.

    personDecoder : Json.Decode.Decoder Person
    personDecoder =
        Json.Decode.map2 Person
            (Json.Decode.field "name" Json.Decode.String)
            (Json.Decode.field "age" Int)
    


  2. Tentukan bidang wajib
    Mereka terkadang menyebutnya "catatan yang dapat diperluas", yang dapat menyesatkan. Sintaks ini dapat digunakan untuk menentukan bahwa Anda mengharapkan beberapa record dengan field tertentu. Seperti:

    type alias NamedThing x =
        { x
            | name : String
        }
    
    showName : NamedThing x -> Html msg
    showName thing =
        Html.text thing.name
    

    Kemudian Anda dapat menggunakan fungsi di atas seperti ini (misalnya dalam tampilan Anda):

    let
        joe = { name = "Joe", age = 34 }
    in
        showName joe
    

    Pembicaraan Richard Feldman tentang ElmEurope 2017 dapat memberikan wawasan lebih jauh tentang kapan gaya ini layak digunakan.

  3. Mengganti nama barang
    Anda dapat melakukan ini, karena nama baru dapat memberikan arti tambahan di kemudian hari dalam kode Anda, seperti dalam contoh ini

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id
    

    Mungkin contoh yang lebih baik dari jenis penggunaan inti ini adalahTime .

  4. Mengekspos ulang tipe dari modul lain
    Jika Anda menulis sebuah paket (bukan aplikasi), Anda mungkin perlu mengimplementasikan tipe dalam satu modul, mungkin dalam modul internal (tidak terekspos), tetapi Anda ingin mengekspos tipe dari modul yang berbeda (publik). Atau, sebagai alternatif, Anda ingin mengekspos tipe Anda dari beberapa modul.
    Taskdi inti dan Http.Request di Http adalah contoh untuk yang pertama, sedangkan pasangan Json.Encode.Value dan Json.Decode.Value adalah contoh di kemudian hari.

    Anda hanya dapat melakukan ini jika sebaliknya Anda ingin mempertahankan tipe buram: Anda tidak mengekspos fungsi konstruktor. Untuk detailnya lihat penggunaan di typebawah ini.

Perlu diperhatikan bahwa dalam contoh di atas hanya # 1 yang menyediakan fungsi konstruktor. Jika Anda mengekspos alias tipe Anda di # 1 seperti module Data exposing (Person)itu akan mengekspos nama tipe serta fungsi konstruktor.



Penggunaan type

  1. Definisikan tipe serikat yang diberi tag
    Ini adalah kasus penggunaan yang paling umum, contoh yang baik adalah Maybetipe dalam inti :

    type Maybe a
        = Just a
        | Nothing
    

    Saat Anda mendefinisikan sebuah tipe, Anda juga mendefinisikan fungsi konstruktornya. Dalam kasus Maybe ini adalah (pseudo-code):

    Just : a -> Maybe a
    
    Nothing : Maybe a
    

    Artinya jika Anda mendeklarasikan nilai ini:

    mayHaveANumber : Maybe Int

    Anda dapat membuatnya dengan salah satunya

    mayHaveANumber = Nothing

    atau

    mayHaveANumber = Just 5

    The Justdan Nothingtag tidak hanya berfungsi sebagai fungsi konstruktor, mereka juga berfungsi sebagai destructors atau pola dalam caseberekspresi. Yang berarti bahwa menggunakan pola-pola ini Anda dapat melihat di dalam a Maybe:

    showValue : Maybe Int -> Html msg
    showValue mayHaveANumber =
        case mayHaveANumber of
            Nothing ->
                Html.text "N/A"
    
            Just number ->
                Html.text (toString number)
    

    Anda dapat melakukan ini, karena modul Maybe didefinisikan seperti

    module Maybe exposing 
        ( Maybe(Just,Nothing)
    

    Bisa juga dikatakan

    module Maybe exposing 
        ( Maybe(..)
    

    Keduanya setara dalam kasus ini, tetapi menjadi eksplisit dianggap kebajikan di Elm, terutama saat Anda menulis paket.


  1. Menyembunyikan detail implementasi
    Seperti yang ditunjukkan di atas, ini adalah pilihan yang disengaja bahwa fungsi konstruktor Maybeterlihat untuk modul lain.

    Namun, ada kasus lain ketika penulis memutuskan untuk menyembunyikannya. Salah satu contohnya adalahDict . Sebagai pengguna paket, Anda seharusnya tidak dapat melihat detail implementasi algoritma pohon Merah / Hitam di belakang Dictdan mengacaukan node secara langsung. Menyembunyikan fungsi konstruktor memaksa konsumen modul / paket Anda untuk hanya membuat nilai jenis Anda (dan kemudian mengubah nilai tersebut) melalui fungsi yang Anda tunjukkan.

    Inilah alasan mengapa terkadang hal-hal seperti ini muncul dalam kode

    type Person =
        Person { name : String, age : Int }
    

    Tidak seperti type aliasdefinisi di bagian atas posting ini, sintaks ini membuat jenis "gabungan" baru dengan hanya satu fungsi konstruktor, tetapi fungsi konstruktor tersebut dapat disembunyikan dari modul / paket lain.

    Jika tipenya diekspos seperti ini:

    module Data exposing (Person)

    Hanya kode dalam Datamodul yang dapat membuat nilai Person dan hanya kode tersebut yang dapat mencocokkan pola padanya.


1

Perbedaan utama, menurut saya, adalah apakah pemeriksa tipe akan berteriak pada Anda jika Anda menggunakan tipe "sinomis".

Buat file berikut, letakkan di suatu tempat dan jalankan elm-reactor, lalu buka http://localhost:8000untuk melihat perbedaannya:

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

Jika Anda menghapus komentar 2.dan berkomentar, 1.Anda akan melihat:

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 aliashanyalah nama pendek untuk beberapa tipe lainnya, serupa classdi OOP. Kedaluwarsa:

type alias Point =
  { x : Int
  , y : Int
  }

Sebuah type(tanpa alias) akan membiarkan Anda menentukan jenis Anda sendiri, sehingga Anda dapat menentukan jenis seperti Int, String, ... untuk Anda aplikasi. Sebagai contoh, dalam kasus umum, ini bisa digunakan untuk mendeskripsikan status aplikasi:

type AppState = 
  Loading          --loading state
  |Loaded          --load successful
  |Error String    --Loading error

Jadi Anda dapat dengan mudah menanganinya di viewelm:

-- VIEW
...
case appState of
    Loading -> showSpinner
    Loaded -> showSuccessData
    Error error -> showError

...

Saya pikir Anda tahu perbedaan antara typedan type alias.

Tapi mengapa dan bagaimana menggunakan typedan type aliaspenting dengan elmaplikasi, kalian dapat merujuk artikel dari Josh Clayton

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.