Differenza in Elm tra tipo e alias di tipo?


93

A Elm, non riesco a capire quando typeè il vs. parola chiave appropriata type alias. La documentazione non sembra avere una spiegazione di questo, né posso trovarne una nelle note di rilascio. È documentato da qualche parte?

Risposte:


136

Come la penso io:

type viene utilizzato per definire nuovi tipi di unione:

type Thing = Something | SomethingElse

Prima di questa definizione Somethinge SomethingElsenon significava nulla. Ora sono entrambi di tipo Thing, che abbiamo appena definito.

type alias è usato per dare un nome a qualche altro tipo che già esiste:

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

{ lat = 5, long = 10 }ha tipo { lat:Int, long:Int }, che era già un tipo valido. Ma ora possiamo anche dire che ha il tipo Locationperché è un alias per lo stesso tipo.

Vale la pena notare che quanto segue verrà compilato e visualizzato correttamente "thing". Anche se specifichiamo thingè a Stringe aliasedStringIdentityprende un AliasedString, non otterremo un errore che ci sia una mancata corrispondenza di tipo tra 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

Non sono sicuro del tuo punto dell'ultimo paragrafo. Stai cercando di dire che sono sempre dello stesso tipo, non importa come lo alias?
ZHANG Cheng

7
Sì,
sto

Quindi quando usi la {}sintassi del record, stai definendo un nuovo tipo?

2
{ lat:Int, long:Int }non definisce un nuovo tipo. È già un tipo valido. type alias Location = { lat:Int, long:Int }inoltre non definisce un nuovo tipo, assegna semplicemente un altro nome (forse più descrittivo) a un tipo già valido.type Location = Geo { lat:Int, long:Int }definirebbe un nuovo tipo ( Location)
robertjlooby

1
Quando si dovrebbe usare il tipo o l'alias di tipo? Dov'è lo svantaggio di usare sempre il tipo?
Richard Haven

8

La chiave è la parola alias. Nel corso della programmazione, quando vuoi raggruppare cose che appartengono insieme, le metti in un record, come nel caso di un punto

{ x = 5, y = 4 }  

o un record dello studente.

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

Ora, se avessi bisogno di passare questi record, dovresti precisare l'intero tipo, come:

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

Se potessi alias un punto, la firma sarebbe molto più facile da scrivere!

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

Quindi un alias è una scorciatoia per qualcos'altro. Qui è una scorciatoia per un tipo di record. Puoi pensarlo come dare un nome a un tipo di record che utilizzerai spesso. Ecco perché si chiama alias: è un altro nome per il tipo di record nudo rappresentato da{ x:Int, y:Int }

Considerando che typerisolve un problema diverso. Se vieni da OOP, è il problema che risolvi con l'ereditarietà, il sovraccarico degli operatori, ecc. - a volte, vuoi trattare i dati come una cosa generica, e talvolta vuoi trattarli come una cosa specifica.

Un luogo comune in cui ciò accade è quando si passano messaggi, come il sistema postale. Quando si invia una lettera, si desidera che il sistema postale tratti tutti i messaggi come la stessa cosa, quindi è necessario progettare il sistema postale una sola volta. Inoltre, il compito di instradare il messaggio dovrebbe essere indipendente dal messaggio contenuto all'interno. È solo quando la lettera raggiunge la sua destinazione che ti interessa qual è il messaggio.

Allo stesso modo, potremmo definire a typecome un'unione di tutti i diversi tipi di messaggi che potrebbero verificarsi. Supponiamo che stiamo implementando un sistema di messaggistica tra studenti universitari e genitori. Quindi ci sono solo due messaggi che i ragazzi del college possono inviare: "Ho bisogno di soldi per la birra" e "Ho bisogno di mutande".

type MessageHome = NeedBeerMoney | NeedUnderpants

Quindi ora, quando progettiamo il sistema di routing, i tipi per le nostre funzioni possono semplicemente passare in giro MessageHome, invece di preoccuparsi di tutti i diversi tipi di messaggi che potrebbero essere. Al sistema di routing non interessa. Ha solo bisogno di sapere che è un file MessageHome. È solo quando il messaggio raggiunge la sua destinazione, la casa del genitore, che devi capire di cosa si tratta.

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

Se conosci l'architettura Elm, la funzione di aggiornamento è una gigantesca istruzione case, perché questa è la destinazione in cui il messaggio viene instradato e quindi elaborato. E usiamo i tipi di unione per avere un solo tipo da trattare quando passiamo il messaggio, ma poi possiamo usare un'istruzione case per estrarre esattamente quale messaggio fosse, così possiamo affrontarlo.


5

Consentitemi di completare le risposte precedenti concentrandomi sui casi d'uso e fornendo un piccolo contesto sulle funzioni e sui moduli del costruttore.



Usi di type alias

  1. Creare un alias e una funzione di costruzione per un record
    Questo è il caso d'uso più comune: è possibile definire un nome alternativo e una funzione di costruzione per un particolare tipo di formato di record.

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

    La definizione dell'alias di tipo implica automaticamente la seguente funzione di costruzione (pseudo codice):
    Person : String -> Int -> { name : String, age : Int }
    può tornare utile, ad esempio quando si desidera scrivere un decodificatore Json.

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


  2. Specificare i campi obbligatori
    A volte lo chiamano "record estensibili", il che può essere fuorviante. Questa sintassi può essere utilizzata per specificare che ci si aspetta qualche record con particolari campi presenti. Ad esempio:

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

    Quindi puoi usare la funzione sopra in questo modo (ad esempio nella tua vista):

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

    Il discorso di Richard Feldman su ElmEurope 2017 potrebbe fornire ulteriori informazioni su quando vale la pena utilizzare questo stile.

  3. Rinominare cose
    Potresti farlo, perché i nuovi nomi potrebbero fornire un significato aggiuntivo più avanti nel tuo codice, come in questo esempio

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

    Forse un esempio migliore di questo tipo di utilizzo nel core èTime .

  4. Riesposizione di un tipo da un modulo diverso
    Se stai scrivendo un pacchetto (non un'applicazione), potresti dover implementare un tipo in un modulo, magari in un modulo interno (non esposto), ma vuoi esporre il tipo da un modulo (pubblico) diverso. Oppure, in alternativa, vuoi esporre il tuo tipo da più moduli.
    Taskin core e Http.Request in Http sono esempi per il primo, mentre Json.Encode.Value e Json.Decode.Value coppia è un esempio del secondo.

    Puoi farlo solo quando vuoi altrimenti mantenere il tipo opaco: non esponi le funzioni del costruttore. Per i dettagli vedere gli usi di typeseguito.

Vale la pena notare che negli esempi precedenti solo # 1 fornisce una funzione di costruzione. Se esponi il tuo alias di tipo in # 1 in module Data exposing (Person)questo modo, esporrà il nome del tipo e la funzione di costruzione.



Usi di type

  1. Definire un tipo di unione con tag
    Questo è il caso d'uso più comune, un buon esempio è il Maybetipo in core :

    type Maybe a
        = Just a
        | Nothing

    Quando si definisce un tipo, si definiscono anche le sue funzioni di costruzione. In caso di Forse questi sono (pseudo-codice):

    Just : a -> Maybe a
    
    Nothing : Maybe a

    Ciò significa che se dichiari questo valore:

    mayHaveANumber : Maybe Int

    Puoi crearlo da entrambi

    mayHaveANumber = Nothing

    o

    mayHaveANumber = Just 5

    I tag Juste Nothingnon servono solo come funzioni di costruzione, ma servono anche come distruttori o modelli in caseun'espressione. Ciò significa che usando questi modelli puoi vedere all'interno di un Maybe:

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

    Puoi farlo, perché il modulo Forse è definito come

    module Maybe exposing 
        ( Maybe(Just,Nothing)

    Si potrebbe anche dire

    module Maybe exposing 
        ( Maybe(..)

    I due sono equivalenti in questo caso, ma essere espliciti è considerata una virtù in Elm, soprattutto quando si scrive un pacchetto.


  1. Nascondere i dettagli di implementazione
    Come sottolineato sopra, è una scelta deliberata di cui le funzioni del costruttore Maybesono visibili per altri moduli.

    Ci sono altri casi, tuttavia, in cui l'autore decide di nasconderli. Un esempio di questo nel core èDict . Come utente del pacchetto, non dovresti essere in grado di vedere i dettagli di implementazione dell'algoritmo ad albero rosso / nero dietro Dicte fare confusione direttamente con i nodi. Nascondere le funzioni del costruttore costringe il consumatore del tuo modulo / pacchetto a creare solo valori del tuo tipo (e quindi trasformare quei valori) attraverso le funzioni che esponi.

    Questo è il motivo per cui a volte cose come questa appaiono nel codice

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

    A differenza della type aliasdefinizione all'inizio di questo post, questa sintassi crea un nuovo tipo di "unione" con una sola funzione di costruzione, ma quella funzione di costruzione può essere nascosta da altri moduli / pacchetti.

    Se il tipo è esposto in questo modo:

    module Data exposing (Person)

    Solo il codice nel Datamodulo può creare un valore Person e solo quel codice può corrispondere al pattern.


1

La differenza principale, per come la vedo io, è se il controllo del tipo ti sgriderà se usi il tipo "sinomico".

Crea il seguente file, mettilo da qualche parte ed esegui elm-reactor, quindi vai a http://localhost:8000per vedere la differenza:

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

Se rimuovi il 2.commento e commenti 1.vedrai:

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 aliasè solo un nome più breve per un altro tipo, simile classin OOP. Exp:

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

Un type(senza alias) ti consente di definire un tipo, in modo da poter definire i tipi come Int, String, ... per voi app. Ad esempio, nel caso comune, può essere utilizzato per la descrizione di uno stato dell'app:

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

Quindi puoi gestirlo facilmente in viewolmo:

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

...

Penso che tu sappia la differenza tra typee type alias.

Ma perché e come usare typeed type aliasè importante con l' elmapp, voi ragazzi potete fare riferimento all'articolo di Josh Clayton

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.