Unterschied in Elm zwischen Typ und Typalias?


93

In Elm, kann ich nicht herausfinden , wenn typedas entsprechende Schlüsselwort vs. ist type alias. Die Dokumentation scheint keine Erklärung dafür zu haben, und ich kann auch keine in den Versionshinweisen finden. Ist das irgendwo dokumentiert?

Antworten:


136

Wie ich es mir vorstelle:

type wird zum Definieren neuer Vereinigungstypen verwendet:

type Thing = Something | SomethingElse

Vor dieser Definition Somethingund SomethingElsebedeutete 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, Locationda 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 , thingist ein Stringund aliasedStringIdentitynimmt 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

Ich bin mir nicht sicher über deinen Punkt im letzten Absatz. Versuchen Sie zu sagen, dass sie immer noch vom selben Typ sind, egal wie Sie es aliasen?
ZHANG Cheng

7
Ja, ich möchte nur darauf hinweisen, dass der Compiler den Alias-Typ als den gleichen wie den ursprünglichen betrachtet
robertjlooby

Wenn Sie also die {}Datensatzsyntax verwenden, definieren Sie einen neuen Typ?

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

1
Wann sollte man Typ vs. Typ Alias ​​verwenden? Wo ist der Nachteil, immer Typ zu verwenden?
Richard Haven

8

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 typeein 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 typeals 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.


5

Lassen Sie mich die vorherigen Antworten ergänzen, indem ich mich auf Anwendungsfälle konzentriere und einen kleinen Kontext zu Konstruktorfunktionen und -modulen gebe.



Verwendung von type alias

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


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

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

  4. 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.
    Taskin 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 typeunten.

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.



Verwendung von type

  1. Definieren eines markierten Vereinigungstyps
    Dies ist der häufigste Anwendungsfall. Ein gutes Beispiel dafür ist der MaybeTyp 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 Justund Nothingdienen nicht nur als Konstruktorfunktionen, sondern auch als Destruktoren oder Muster in einem caseAusdruck. 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.


  1. Ausblenden von Implementierungsdetails
    Wie oben erwähnt, ist es eine bewusste Entscheidung, dass die Konstruktorfunktionen von Maybefü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 Dictund 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 aliasDefinition 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 DataModul kann einen Personenwert erstellen, und nur dieser Code kann Musterübereinstimmungen erstellen.


1

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

0

An aliasist nur ein kürzerer Name für einen anderen Typ, ähnlich wie classin 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 viewUlme handhaben :

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

...

Ich denke du kennst den Unterschied zwischen typeund type alias.

Aber warum und wie man es benutzt typeund type aliaswas mit der elmApp wichtig ist , ihr könnt den Artikel von Josh Clayton lesen

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.