Antworten:
Wie ich es mir vorstelle:
type
wird zum Definieren neuer Vereinigungstypen verwendet:
type Thing = Something | SomethingElse
Vor dieser Definition Something
und SomethingElse
bedeutete nichts. Jetzt sind beide vom Typ Thing
, den wir gerade definiert haben.
type alias
wird verwendet, um einem anderen bereits vorhandenen Typ einen Namen zu geben:
type alias Location = { lat:Int, long:Int }
{ lat = 5, long = 10 }
hat Typ { lat:Int, long:Int }
, der bereits ein gültiger Typ war. Jetzt können wir aber auch sagen, dass es einen Typ hat, Location
da dies ein Alias für denselben Typ ist.
Es ist erwähnenswert, dass das Folgende gut kompiliert und angezeigt wird "thing"
. Auch wenn wir angeben , thing
ist ein String
und aliasedStringIdentity
nimmt ein AliasedString
, wir werden nicht einen Fehler , dass es einen Typenkonflikt zwischen 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 }
definiert keinen neuen Typ. Das ist schon ein gültiger Typ. type alias Location = { lat:Int, long:Int }
definiert auch keinen neuen Typ, sondern gibt einem bereits gültigen Typ nur einen anderen (möglicherweise aussagekräftigeren) Namen. type Location = Geo { lat:Int, long:Int }
würde einen neuen Typ definieren ( Location
)
Der Schlüssel ist das Wort alias
. Wenn Sie im Verlauf der Programmierung Dinge gruppieren möchten, die zusammengehören, fügen Sie sie in einen Datensatz ein, wie im Fall eines Punktes
{ x = 5, y = 4 }
oder eine Studentenakte.
{ name = "Billy Bob", grade = 10, classof = 1998 }
Wenn Sie diese Datensätze weitergeben müssen, müssen Sie den gesamten Typ wie folgt buchstabieren:
add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
{ a.x + b.x, a.y + b.y }
Wenn Sie einen Punkt aliasen könnten, wäre die Signatur viel einfacher zu schreiben!
type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
{ a.x + b.x, a.y + b.y }
Ein Alias ist also eine Abkürzung für etwas anderes. Hier ist es eine Abkürzung für einen Datensatztyp. Sie können sich vorstellen, einem Datensatztyp, den Sie häufig verwenden, einen Namen zu geben. Aus diesem Grund wird es als Alias bezeichnet - es ist ein anderer Name für den nackten Datensatztyp, der durch dargestellt wird{ x:Int, y:Int }
Löst type
ein anderes Problem. Wenn Sie von OOP kommen, ist dies das Problem, das Sie mit Vererbung, Überladung von Operatoren usw. lösen. Manchmal möchten Sie die Daten als generische Sache behandeln, und manchmal möchten Sie sie wie eine bestimmte Sache behandeln.
Ein alltäglicher Ort, an dem dies geschieht, ist das Weitergeben von Nachrichten - wie das Postsystem. Wenn Sie einen Brief senden, soll das Postsystem alle Nachrichten als gleich behandeln, sodass Sie das Postsystem nur einmal entwerfen müssen. Außerdem sollte die Weiterleitung der Nachricht unabhängig von der darin enthaltenen Nachricht sein. Erst wenn der Brief sein Ziel erreicht, interessiert es Sie, was die Nachricht ist.
Auf die gleiche Weise können wir a type
als eine Vereinigung aller verschiedenen Arten von Nachrichten definieren, die auftreten können. Angenommen, wir implementieren ein Nachrichtensystem zwischen Studenten und ihren Eltern. Es gibt also nur zwei Nachrichten, die College-Kinder senden können: "Ich brauche Biergeld" und "Ich brauche Unterhosen".
type MessageHome = NeedBeerMoney | NeedUnderpants
Wenn wir nun das Routing-System entwerfen, können die Typen für unsere Funktionen einfach weitergegeben werden MessageHome
, anstatt sich über die verschiedenen Arten von Nachrichten Gedanken zu machen. Das Routing-System kümmert sich nicht darum. Es muss nur wissen, dass es ein ist MessageHome
. Erst wenn die Nachricht ihr Ziel erreicht, das Zuhause der Eltern, müssen Sie herausfinden, was es ist.
case message of
NeedBeerMoney ->
sayNo()
NeedUnderpants ->
sendUnderpants(3)
Wenn Sie die Elm-Architektur kennen, ist die Aktualisierungsfunktion eine riesige Fallanweisung, da dies das Ziel ist, an das die Nachricht weitergeleitet und somit verarbeitet wird. Und wir verwenden Union-Typen, um einen einzelnen Typ zu haben, mit dem wir uns beim Weitergeben der Nachricht befassen können, können dann aber eine case-Anweisung verwenden, um genau herauszufinden, um welche Nachricht es sich handelt, damit wir damit umgehen können.
Lassen Sie mich die vorherigen Antworten ergänzen, indem ich mich auf Anwendungsfälle konzentriere und einen kleinen Kontext zu Konstruktorfunktionen und -modulen gebe.
type alias
Erstellen eines Alias und einer Konstruktorfunktion für einen Datensatz
Dies ist der häufigste Anwendungsfall: Sie können einen alternativen Namen und eine Konstruktorfunktion für eine bestimmte Art von Datensatzformat definieren.
type alias Person =
{ name : String
, age : Int
}
Das Definieren des Typalias impliziert automatisch die folgende Konstruktorfunktion (Pseudocode):
Person : String -> Int -> { name : String, age : Int }
Dies kann nützlich sein, beispielsweise wenn Sie einen Json-Decoder schreiben möchten.
personDecoder : Json.Decode.Decoder Person
personDecoder =
Json.Decode.map2 Person
(Json.Decode.field "name" Json.Decode.String)
(Json.Decode.field "age" Int)
Geben
Sie die erforderlichen Felder an. Sie nennen es manchmal "erweiterbare Datensätze", was irreführend sein kann. Diese Syntax kann verwendet werden, um anzugeben, dass Sie einen Datensatz mit bestimmten vorhandenen Feldern erwarten. Sowie:
type alias NamedThing x =
{ x
| name : String
}
showName : NamedThing x -> Html msg
showName thing =
Html.text thing.name
Dann können Sie die obige Funktion wie folgt verwenden (zum Beispiel aus Ihrer Sicht):
let
joe = { name = "Joe", age = 34 }
in
showName joe
Richard Feldmans Vortrag auf ElmEurope 2017 könnte einen weiteren Einblick geben, wann sich dieser Stil lohnt.
Umbenennen von Inhalten
Sie können dies tun, da die neuen Namen später in Ihrem Code eine zusätzliche Bedeutung haben können, wie in diesem Beispiel
type alias Id = String
type alias ElapsedTime = Time
type SessionStatus
= NotStarted
| Active Id ElapsedTime
| Finished Id
Vielleicht ist ein besseres Beispiel für diese Art der Verwendung im KernTime
.
Erneutes Offenlegen eines Typs aus einem anderen Modul
Wenn Sie ein Paket (keine Anwendung) schreiben, müssen Sie möglicherweise einen Typ in einem Modul implementieren, möglicherweise in einem internen (nicht belichteten) Modul, aber Sie möchten den Typ aus einem anderen Modul verfügbar machen ein anderes (öffentliches) Modul. Alternativ können Sie Ihren Typ aus mehreren Modulen verfügbar machen.
Task
in core und Http.Request in Http sind Beispiele für das erste, während das Paar Json.Encode.Value und Json.Decode.Value ein Beispiel für das spätere ist.
Sie können dies nur tun, wenn Sie den Typ ansonsten undurchsichtig halten möchten: Sie machen die Konstruktorfunktionen nicht verfügbar. Für Details siehe Verwendungen von type
unten.
Es ist anzumerken, dass in den obigen Beispielen nur # 1 eine Konstruktorfunktion bereitstellt. Wenn Sie Ihren Typalias in # 1 auf diese Weise verfügbar machen module Data exposing (Person)
, werden sowohl der Typname als auch die Konstruktorfunktion verfügbar gemacht.
type
Definieren eines markierten Vereinigungstyps
Dies ist der häufigste Anwendungsfall. Ein gutes Beispiel dafür ist der Maybe
Typ im Kern :
type Maybe a
= Just a
| Nothing
Wenn Sie einen Typ definieren, definieren Sie auch seine Konstruktorfunktionen. Im Falle von Vielleicht sind dies (Pseudocode):
Just : a -> Maybe a
Nothing : Maybe a
Was bedeutet, wenn Sie diesen Wert deklarieren:
mayHaveANumber : Maybe Int
Sie können es entweder erstellen
mayHaveANumber = Nothing
oder
mayHaveANumber = Just 5
Die Tags Just
und Nothing
dienen nicht nur als Konstruktorfunktionen, sondern auch als Destruktoren oder Muster in einem case
Ausdruck. Was bedeutet, dass Sie mit diesen Mustern in einem sehen können Maybe
:
showValue : Maybe Int -> Html msg
showValue mayHaveANumber =
case mayHaveANumber of
Nothing ->
Html.text "N/A"
Just number ->
Html.text (toString number)
Sie können dies tun, da das Vielleicht-Modul wie folgt definiert ist
module Maybe exposing
( Maybe(Just,Nothing)
Es könnte auch sagen
module Maybe exposing
( Maybe(..)
Die beiden sind in diesem Fall gleichwertig, aber explizit zu sein wird in Elm als Tugend angesehen, insbesondere wenn Sie ein Paket schreiben.
Ausblenden von Implementierungsdetails
Wie oben erwähnt, ist es eine bewusste Entscheidung, dass die Konstruktorfunktionen von Maybe
für andere Module sichtbar sind.
Es gibt jedoch auch andere Fälle, in denen der Autor beschließt, sie auszublenden. Ein Beispiel hierfür ist im KernDict
. Als Konsument des Pakets sollten Sie nicht in der Lage sein, die Implementierungsdetails des Rot / Schwarz-Baum-Algorithmus dahinter zu sehen Dict
und direkt mit den Knoten zu spielen. Durch das Ausblenden der Konstruktorfunktionen wird der Benutzer Ihres Moduls / Pakets gezwungen, nur Werte Ihres Typs über die von Ihnen bereitgestellten Funktionen zu erstellen (und diese Werte dann zu transformieren).
Dies ist der Grund, warum manchmal solche Dinge im Code erscheinen
type Person =
Person { name : String, age : Int }
Im Gegensatz zur type alias
Definition oben in diesem Beitrag erstellt diese Syntax einen neuen "Union" -Typ mit nur einer Konstruktorfunktion, diese Konstruktorfunktion kann jedoch vor anderen Modulen / Paketen verborgen werden.
Wenn der Typ wie folgt belichtet wird:
module Data exposing (Person)
Nur Code im Data
Modul kann einen Personenwert erstellen, und nur dieser Code kann Musterübereinstimmungen erstellen.
Der Hauptunterschied besteht meines Erachtens darin, ob die Typprüfung Sie anschreit, wenn Sie den Typ "synomisch" verwenden.
Erstellen Sie die folgende Datei, platzieren Sie sie irgendwo und führen Sie sie aus elm-reactor
. Gehen Sie dann zu http://localhost:8000
, um den Unterschied zu sehen:
-- 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
Wenn Sie 2.
kommentieren und kommentieren, werden 1.
Sie sehen:
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
ist nur ein kürzerer Name für einen anderen Typ, ähnlich wie class
in OOP. Exp:
type alias Point =
{ x : Int
, y : Int
}
Ein type
(ohne Alias) können Sie Ihre eigene Art zu definieren, so dass Sie Typen wie definieren können Int
, String
... für Sie App. Zum Beispiel kann es im allgemeinen Fall zur Beschreibung eines App-Status verwendet werden:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
So können Sie es einfach in view
Ulme handhaben :
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
Ich denke du kennst den Unterschied zwischen type
und type alias
.
Aber warum und wie man es benutzt type
und type alias
was mit der elm
App wichtig ist , ihr könnt den Artikel von Josh Clayton lesen