Suche…


Bemerkungen

Bitte spielen Sie selbst mit diesen Konzepten, um sie wirklich zu meistern! Der elm-repl (siehe Einleitung zur REPL ) ist wahrscheinlich ein guter Ort, um mit dem obigen Code elm-repl . Sie können auch online mit elm-repl .

Vergleichbare Datentypen

Vergleichbare Typen sind Grundtypen, die mit Vergleichsoperatoren aus dem Basics- Modul verglichen werden können, wie: (<) , (>) , (<=) , (>=) , max , min , compare

Vergleichbare Typen in Elm sind Int , Float , Time , Char , String und Tupel oder Listen vergleichbarer Typen.

In Dokumentationen oder Typdefinitionen werden sie als comparable spezielle Typvariable bezeichnet, z. Siehe Typdefinition für die Basics.max Funktion:

max : comparable -> comparable -> comparable

Typ Unterschriften

In Elm werden Werte durch Schreiben eines Namens, eines Gleichheitszeichens und dann des tatsächlichen Werts deklariert:

someValue = 42

Funktionen sind auch Werte, wobei zusätzlich ein Wert oder Werte als Argumente verwendet werden. Sie werden normalerweise wie folgt geschrieben:

double n = n * 2

Jeder Wert in Elm hat einen Typ. Die Typen der oben beschriebenen Werte werden durch den Compiler abgeleitet werden , je nachdem , wie sie verwendet werden. Es empfiehlt sich jedoch, den Typ eines Wertes der obersten Ebene immer explizit anzugeben. Dazu schreiben Sie eine Typensignatur wie folgt:

someValue : Int
someValue = 
    42

someOtherValue : Float
someOtherValue =
    42

Wie wir sehen können, kann 42 entweder als Int oder als Float . Dies ist intuitiv sinnvoll. Weitere Informationen finden Sie unter Typvariablen .

Typunterschriften sind besonders nützlich, wenn sie mit Funktionen verwendet werden. Hier ist die Verdopplungsfunktion von vor:

double : Int -> Int
double n =
    n * 2

Diesmal hat die Signatur ein -> , einen Pfeil, und wir würden die Signatur als "int bis int" aussprechen oder "eine ganze Zahl nehmen und eine ganze Zahl zurückgeben". -> gibt an, dass , indem sie double einen Int - Wert als Argument, double wird eine Rückkehr Int . Daher braucht es eine ganze Zahl zu einer ganzen Zahl:

> double
<function> : Int -> Int

> double 3
6 : Int

Grundtypen

elm-repl in elm-repl einen Code ein, um den Wert und den abgeleiteten Typ elm-repl . Versuchen Sie Folgendes, um die verschiedenen Typen kennenzulernen:

> 42
42 : number

> 1.987
1.987 : Float

> 42 / 2
21 : Float

> 42 % 2
0 : Int

> 'e'
'e' : Char

> "e"
"e" : String

> "Hello Friend"
"Hello Friend" : String

> ['w', 'o', 'a', 'h']
['w', 'o', 'a', 'h'] : List Char

> ("hey", 42.42, ['n', 'o'])
("hey", 42.42, ['n', 'o']) : ( String, Float, List Char )

> (1, 2.1, 3, 4.3, 'c')
(1,2.1,3,4.3,'c') : ( number, Float, number', Float, Char )

> {}
{} : {}

> { hey = "Hi", someNumber = 43 }
{ hey = "Hi", someNumber = 43 } : { hey : String, someNumber : number }

> ()
() : ()

{} ist der leere Record-Typ und () ist der leere Tuple-Typ. Letzteres wird häufig zum Zweck der faulen Bewertung verwendet. Siehe das entsprechende Beispiel in Funktionen und Teilanwendung .

Beachten Sie, wie die number aktiviert erscheint. Dies zeigt an, dass es sich um eine Art veränderlich ist, und darüber hinaus die bestimmte number bezieht sich auf eine spezielle Art Variable , die entweder eine sein , Int oder ein Float (siehe entsprechende Abschnitte für mehr). Typen sind jedoch immer Großbuchstaben wie Char , Float , List String usw.

Typvariablen

Typvariablen sind nicht kapitalisierte Namen in Typensignaturen. Im Gegensatz zu ihren kapitalisierten Gegenstücken, wie z. B. Int und String , repräsentieren sie nicht einen einzigen Typ, sondern einen beliebigen Typ. Sie werden verwendet, um generische Funktionen zu schreiben, die für jeden Typ und jeden Typ geeignet sind, und sind besonders nützlich, wenn Operationen über Container wie List oder Dict . Die Funktion List.reverse hat beispielsweise die folgende Signatur:

reverse : List a -> List a

Dies bedeutet, dass eine Liste mit einem beliebigen Typwert verwendet werden kann. Daher können List Int , List (List String) sowohl von diesen als auch von allen anderen reversed . Daher ist a eine Typvariable, die für jeden Typ stehen kann.

Die reverse Funktion haben könnte jeden uncapitalized Variablennamen in seiner Art Unterschrift, mit Ausnahme der wenige speziellen Typ Variablennamen, wie verwendet number (siehe das entsprechende Beispiel auf , dass für weitere Informationen):

reverse : List lol -> List lol

reverse : List wakaFlaka -> List wakaFlaka

Die Namen von Typvariablen werden nur dann sinnvoll, wenn innerhalb einer einzelnen Signatur verschiedene Typvariablen vorhanden sind, beispielsweise durch die map Funktion in Listen:

map : (a -> b) -> List a -> List b

map nimmt eine Funktion von einem beliebigen Typ a an einen beliebigen Typ b sowie eine Liste mit Elementen eines Typs a und gibt eine Liste von Elementen eines Typs b , die durch Anwenden der angegebenen Funktion auf jedes Element der Liste abgerufen wird.

Lassen Sie uns die Signatur konkretisieren, um das besser zu sehen:

plusOne : Int -> Int
plusOne x = 
    x + 1

> List.map plusOne
<function> : List Int -> List Int

Wie wir sehen können, sind in diesem Fall sowohl a = Int als auch b = Int . Wenn map jedoch eine Typensignatur wie map : (a -> a) -> List a -> List a , funktioniert sie nur mit Funktionen, die nur einen einzigen Typ betreffen, und Sie können den Typ niemals ändern Typ einer Liste mithilfe der map . Da die Typensignatur von map mehrere verschiedene Typvariablen hat, a und b , können Sie map , um den Typ einer Liste zu ändern:

isOdd : Int -> Bool
isOdd x =
    x % 2 /= 0

> List.map isOdd
<function> : List Int -> List Bool

In diesem Fall ist a = Int und b = Bool . Damit Sie Funktionen verwenden können, die verschiedene Typen annehmen und zurückgeben können, müssen Sie verschiedene Typvariablen verwenden.

Geben Sie Aliase ein

Manchmal möchten wir einem Typ einen aussagekräftigeren Namen geben. Nehmen wir an, unsere App hat einen Datentyp, der Benutzer repräsentiert:

{ name : String, age : Int, email : String }

Und unsere Funktionen für Benutzer haben folgende Typunterschriften:

prettyPrintUser : { name : String, age : Int, email : String } -> String

Dies könnte bei einem größeren Datensatztyp für einen Benutzer recht unhandlich werden. Verwenden Sie also einen Typalias , um die Größe zu reduzieren und dieser Datenstruktur einen aussagekräftigeren Namen zu geben:

type alias User =
    { name: String
    , age : Int
    , email : String
    }


prettyPrintUser : User -> String

Typ-Aliasnamen erleichtern die Definition und Verwendung eines Modells für eine Anwendung:

type alias Model =
    { count : Int
    , lastEditMade : Time
    }

Bei der Verwendung von type alias Typ mit dem von Ihnen angegebenen Namen buchstäblich Aliase verwendet. Die Verwendung von Model oben ist genau das gleiche wie mit { count : Int, lastEditMade : Time } . Hier ein Beispiel, das zeigt, wie sich Aliasnamen nicht von den zugrunde liegenden Typen unterscheiden:

type alias Bugatti = Int

type alias Fugazi = Int

unstoppableForceImmovableObject : Bugatti -> Fugazi -> Int
unstoppableForceImmovableObject bug fug =
    bug + fug

> unstoppableForceImmovableObject 09 87
96 : Int

Ein Typalias für einen Datensatztyp definiert eine Konstruktorfunktion mit einem Argument für jedes Feld in Deklarationsreihenfolge.

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

Point 3 7
{ x = 3, y = 7 } : Point

type alias Person = { last : String, middle : String, first : String }

Person "McNameface" "M" "Namey"
{ last = "McNameface", middle = "M", first = "Namey" } : Person

Jeder Datensatztyp-Alias ​​hat auch für einen kompatiblen Typ eine eigene Feldreihenfolge.

type alias Person = { last : String, middle : String, first : String }
type alias Person2 = { first : String, last : String, middle : String }

Person2 "Theodore" "Roosevelt" "-"
{ first = "Theodore", last = "Roosevelt", middle = "-" } : Person2

a = [ Person "Last" "Middle" "First", Person2 "First" "Last" "Middle" ]
[{ last = "Last", middle = "Middle", first = "First" },{ first = "First", last = "Last", middle = "Middle" }] : List Person2

Verbesserung der Typsicherheit durch neue Typen

Aliasing-Typen reduzieren die Boilerplate und verbessern die Lesbarkeit. Sie sind jedoch nicht typsicherer als der Alias-Typ. Folgendes berücksichtigen:

type alias Email = String

type alias Name = String

someEmail = "[email protected]"

someName = "Benedict"

sendEmail : Email -> Cmd msg
sendEmail email = ...

Mit dem obigen Code können wir sendEmail someName schreiben, und es wird kompiliert, auch wenn es eigentlich nicht sollte, denn Namen und E-Mails sind beide String , aber sie sind völlig andere Dinge.

Wir können wirklich einen String von einem anderen String auf der Typebene unterscheiden, indem wir einen neuen Typ erstellen. Hier ein Beispiel, in dem Email als type und nicht als type alias :

module Email exposing (Email, create, send)

type Email = EmailAddress String

isValid : String -> Bool
isValid email = 
  -- ...validation logic

create : String -> Maybe Email
create email =
    if isValid email then
        Just (EmailAddress email)
    else
        Nothing

send : Email -> Cmd msg
send (EmailAddress email) = ...

Unsere isValid Funktion isValid , ob eine Zeichenfolge eine gültige E-Mail-Adresse ist. Die Funktion create prüft, ob ein bestimmter String eine gültige E-Mail ist, und sendet eine Maybe umschlossene Email um sicherzustellen, dass nur validierte Adressen zurückgegeben werden. Wir können die Validierungsprüfung umgehen, indem wir eine Email direkt EmailAddress "somestring" indem EmailAddress "somestring" , wenn unsere Moduldeklaration den EmailAddress Konstruktor nicht wie hier gezeigt EmailAddress

module Email exposing (Email, create, send)

In diesem EmailAddress kein anderes Modul Zugriff auf den EmailAddress Konstruktor, obwohl der Email Typ in Anmerkungen verwendet werden kann. Die einzige Möglichkeit, eine neue Email außerhalb dieses Moduls zu create ist die Verwendung der von ihr bereitgestellten create Diese Funktion stellt sicher, dass nur gültige E-Mail-Adressen zurückgegeben werden. Daher leitet diese API den Benutzer über den Typ "Sicherheit" automatisch in den richtigen Pfad zurück: send funktioniert nur mit von create Werten, die eine Gültigkeitsprüfung durchführen und die Verarbeitung ungültiger E-Mails erzwingen, da eine Maybe Email .

Wenn Sie den Email Konstruktor exportieren Email , können Sie schreiben

module Email exposing (Email(EmailAddress), create, send)

Nun kann jede Datei, die Email importiert, auch ihren Konstruktor importieren. In diesem Fall können Benutzer die Validierung umgehen und ungültige E-Mails send Sie erstellen jedoch nicht immer eine solche API, sodass das Exportieren von Konstruktoren hilfreich sein kann. Bei einem Typ mit mehreren Konstruktoren möchten Sie möglicherweise auch nur einige davon exportieren.

Typen konstruieren

Die Schlüsselwortkombination des type alias gibt einen neuen Namen für einen Typ an, das Schlüsselwort type isoliert jedoch einen neuen Typ. Untersuchen wir einen der grundlegendsten dieser Typen: Maybe

type Maybe a
    = Just a
    | Nothing

Das erste , was zu beachten ist , dass der Maybe Typ mit einer deklarieren Variable vom Typ von a . Die zweite Bemerkung ist das Pipe-Zeichen | , was "oder" bedeutet. Mit anderen Worten, etwas vom Typ Maybe a ist entweder Just a oder Nothing .

Wenn Sie den obigen Code zu schreiben, Just und Nothing kommen in Umfang als wertBauer, und Maybe kommt in Umfang als Typ-Konstruktor. Dies sind ihre Unterschriften:

Just : a -> Maybe a

Nothing : Maybe a

Maybe : a -> Maybe a -- this can only be used in type signatures

Aufgrund der Art Variable a kann jede Art „ nach innen eingewickelt“ werden von der Maybe geben. Also, Maybe Int , Maybe (List String) oder Maybe (Maybe (List Html)) alle gültigen Typen. Wenn jeder Destrukturierung type mit einem Wert case Ausdruck, müssen Sie für jede mögliche Instanziierung dieser Art erklären. Im Falle eines Werts vom Typ Maybe a müssen Sie sowohl den Fall Just a Fall als auch den Fall Nothing berücksichtigen:

thing : Maybe Int
thing = 
    Just 3

blah : Int
blah =
    case thing of
        Just n -> 
            n

        Nothing ->
            42

-- blah = 3

Versuchen Sie, den obigen Code ohne die Nothing Klausel im case Ausdruck zu schreiben: Er wird nicht kompiliert. Dies macht den Typ-Konstruktor von Maybe einem hervorragenden Muster zum Ausdrücken von Werten, die möglicherweise nicht vorhanden sind, da Sie dazu gezwungen werden, mit der Logik umzugehen, wenn der Wert Nothing .

Der Niemals-Typ

Der Typ " Never " kann nicht erstellt werden (das Basics Modul hat seinen Wertkonstruktor nicht exportiert und Ihnen keine andere Funktion gegeben, die auch " Never zurückgibt). Es gibt keinen Wert never : Never oder eine Funktion createNever : ?? -> Never .

Das hat seine Vorteile: Sie können in einem Typensystem eine Möglichkeit kodieren, die nicht möglich ist. Dies kann in Typen wie Task Never Int die garantieren, dass es mit einem Int gelingt. oder Program Never , das beim Initialisieren des Elm-Codes aus JavaScript keine Parameter übernimmt.

Spezielle Typvariablen

Elm definiert die folgenden speziellen Typvariablen, die für den Compiler eine bestimmte Bedeutung haben:

  • comparable : Bestehend aus Int , Float , Char , String und Tupeln davon. Dies ermöglicht die Verwendung der Operatoren < und > .

    Beispiel: Sie können eine Funktion definieren, um die kleinsten und größten Elemente in einer Liste ( extent ) zu finden. Sie denken, welche Art von Signatur Sie schreiben. Auf der einen Seite könnten Sie extentInt : List Int -> Maybe (Int, Int) schreiben extentInt : List Int -> Maybe (Int, Int) und extentChar : List Char -> Maybe (Char, Char) und ein weiteres für Float und String . Die Umsetzung dieser wäre die gleiche:

    extentInt list =
      let
        helper x (minimum, maximum) = 
          ((min minimum x), (max maximum x))
      in 
        case list of 
          [] ->
            Nothing
          x :: xs ->
            Just <| List.foldr helper (x, x) xs
    

    Sie könnten versucht sein, einfach einen Bereich zu schreiben extent : List a -> Maybe (a, a) , aber der Compiler lässt Sie das nicht, da die Funktionen min und max für diese Typen nicht definiert sind (Achtung: Dies sind nur einfache Wrapper um den oben genannten < Operator). Sie können dieses Problem lösen, indem Sie den extent : List comparable -> Maybe (comparable, comparable) definieren extent : List comparable -> Maybe (comparable, comparable) . Dadurch kann Ihre Lösung polymorph sein , was bedeutet, dass sie für mehrere Typen geeignet ist.

  • number : Bestehend aus Int und Float . Ermöglicht die Verwendung von arithmetischen Operatoren mit Ausnahme der Division. Sie können dann beispielsweise die sum : List number -> number definieren sum : List number -> number und für Ints und Floats funktionieren lassen.

  • appendable : appendable aus String , List . Erlaubt die Verwendung des ++ Operators.

  • compappend : Dies erscheint manchmal, ist jedoch ein Implementierungsdetail des Compilers. Derzeit kann dies nicht in Ihren eigenen Programmen verwendet werden, wird aber manchmal erwähnt.

Beachten Sie, dass in einer Typanmerkung wie folgt: number -> number -> number alle auf denselben Typ beziehen, so dass das Übergeben von Int -> Float -> Int ein Int -> Float -> Int wäre. Sie können dieses Problem lösen, indem Sie dem Typvariablennamen ein Suffix hinzufügen: number -> number' -> number'' würde dann gut kompilieren.

Es gibt keinen offiziellen Namen dafür, sie werden manchmal genannt:

  • Spezielle Typvariablen
  • Typklassentypartige Typvariablen
  • Pseudo-Typ-Klassen

Dies liegt daran, dass sie wie die Typenklassen von Haskell arbeiten, ohne dass der Benutzer diese definieren kann.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow