Risposte:
Come la penso io:
type
viene utilizzato per definire nuovi tipi di unione:
type Thing = Something | SomethingElse
Prima di questa definizione Something
e SomethingElse
non 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 Location
perché è un alias per lo stesso tipo.
Vale la pena notare che quanto segue verrà compilato e visualizzato correttamente "thing"
. Anche se specifichiamo thing
è a String
e aliasedStringIdentity
prende 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
{ 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
)
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 type
risolve 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 type
come 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.
Consentitemi di completare le risposte precedenti concentrandomi sui casi d'uso e fornendo un piccolo contesto sulle funzioni e sui moduli del costruttore.
type alias
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)
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.
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
.
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.
Task
in 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 type
seguito.
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.
type
Definire un tipo di unione con tag
Questo è il caso d'uso più comune, un buon esempio è il Maybe
tipo 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 Just
e Nothing
non servono solo come funzioni di costruzione, ma servono anche come distruttori o modelli in case
un'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.
Nascondere i dettagli di implementazione
Come sottolineato sopra, è una scelta deliberata di cui le funzioni del costruttore Maybe
sono 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 Dict
e 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 alias
definizione 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 Data
modulo può creare un valore Person e solo quel codice può corrispondere al pattern.
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:8000
per 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
An alias
è solo un nome più breve per un altro tipo, simile class
in 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 view
olmo:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
Penso che tu sappia la differenza tra type
e type alias
.
Ma perché e come usare type
ed type alias
è importante con l' elm
app, voi ragazzi potete fare riferimento all'articolo di Josh Clayton