Sök…


Anmärkningar

Lek gärna med dessa koncept själv för att verkligen behärska dem! elm-repl (se Introduktion till REPL ) är förmodligen ett bra ställe att leka med koden ovan. Du kan också spela med elm-repl online .

Jämförbara datatyper

Jämförbara typer är primitiva typer som kan jämföras med jämförande operatörer från basmodulen , som: (<) , (>) , (<=) , (>=) , max , min , compare

Jämförbara typer i Elm är Int , Float , Time , Char , String och tuples eller listor med jämförbara typer.

I dokumentation eller typdefinitioner hänvisas de till en speciell typvariabel som är comparable , t.ex. se Basics.max för Basics.max funktion:

max : comparable -> comparable -> comparable

Skriv signaturer

I Elm deklareras värden genom att skriva ett namn, ett lika tecken och sedan det verkliga värdet:

someValue = 42

Funktioner är också värden, med tillägg av att ta ett värde eller värden som argument. De är vanligtvis skrivna på följande sätt:

double n = n * 2

Varje värde i Elm har en typ. Typerna av värdena ovan kommer att sluts av kompilatorn beroende på hur de används. Men det är bästa praxis att alltid uttryckligen ange typen av toppnivåvärde och att göra det skriver du en typsignatur på följande sätt:

someValue : Int
someValue = 
    42

someOtherValue : Float
someOtherValue =
    42

Som vi ser kan 42 definieras som antingen en Int eller en Float . Detta är intuitivt men se typvariabler för mer information.

Typsignaturer är särskilt värdefulla när de används med funktioner. Här är fördubblingsfunktionen från tidigare:

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

Den här gången har signaturen en -> , en pil, och vi skulle uttala signaturen som "int till int", eller "tar ett heltal och returnerar ett heltal". -> indikerar att genom att ge ett double ett Int värde som ett argument kommer double att returnera ett Int . Därför tar det ett heltal till ett heltal:

> double
<function> : Int -> Int

> double 3
6 : Int

Grundläggande typer

I elm-repl skriver du en kodkod för att få dess värde och slutsatsen. Prova följande för att lära dig om de olika typerna som finns:

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

> ()
() : ()

{} är den tomma posttypen, och () är den tomma typen Tuple. Det sistnämnda används ofta för doven utvärdering. Se motsvarande exempel i funktioner och delvis applikation .

Lägg märke till hur number verkar okapitaliserade. Detta tyder på att det är en typ variabel, och dessutom den speciella ordet number hänvisar till en speciell typ variabel som antingen kan vara en Int eller ett Float (se motsvarande avsnitt för mer). Typer är dock alltid stora och stora bokstäver, till exempel Char , Float , List String , etc.

Typvariabler

Typvariabler är okapitaliserade namn i typsignaturer. Till skillnad från deras aktiverade motsvarigheter, såsom Int och String , representerar de inte en enda typ, utan snarare någon typ. De används för att skriva generiska funktioner som kan fungera på alla typer och typer, och är särskilt användbara för att skriva över containrar som List eller Dict . Funktionen List.reverse har till exempel följande signatur:

reverse : List a -> List a

Vilket innebär att det kan fungera på en lista med valfritt typvärde , så att List Int , List (List String) , både dessa och andra kan reversed på samma sätt. Därför a är en typ variabel som kan stå i för alla typer.

Den reverse funktionen kunde ha använt valfri okapitaliserad variabelnamn i sin typsignatur, med undantag för en handfull variabelnamn för specialtyp , till exempel number (se motsvarande exempel på det för mer information):

reverse : List lol -> List lol

reverse : List wakaFlaka -> List wakaFlaka

Namnen på typ variabler blir meningsfull endast när det när det finns olika typer variabler inom en och samma signatur, som exemplifieras av map funktionen listor:

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

map tar någon funktion från vilken typ som helst a till vilken typ b helst, tillsammans med en lista med element av någon typ a , och returnerar en lista med element av någon typ b , som den får genom att tillämpa den givna funktionen på varje element i listan.

Låt oss göra signaturen konkret för att bättre se detta:

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

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

Som vi ser är både a = Int och b = Int i detta fall. Men om map hade en typsignatur som map : (a -> a) -> List a -> List a , skulle den bara fungera på funktioner som fungerar på en enda typ, och du skulle aldrig kunna ändra skriv en lista med hjälp av map funktionen. Men eftersom typsignaturen på map har flera olika typvariabler, a och b , kan vi använda map att ändra listans typ:

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

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

I detta fall a = Int och b = Bool . Därför måste du använda olika typvariabler för att kunna använda funktioner som kan ta och returnera olika typer.

Skriv alias

Ibland vill vi ge en typ ett mer beskrivande namn. Låt oss säga att vår app har en datatyp som representerar användare:

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

Och våra funktioner för användare har typsignaturer enligt:

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

Detta kan bli ganska svårt med en större posttyp för en användare, så låt oss använda ett typalias för att skära ned på storleken och ge ett mer meningsfullt namn på den datastrukturen:

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


prettyPrintUser : User -> String

Typ alias gör det mycket renare att definiera och använda en modell för en applikation:

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

Att använda type alias bokstavligen bara alias en typ med namnet du ger den. Med användning av Model typ ovan är exakt samma som att använda { count : Int, lastEditMade : Time } . Här är ett exempel som visar hur alias inte skiljer sig från de underliggande typerna:

type alias Bugatti = Int

type alias Fugazi = Int

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

> unstoppableForceImmovableObject 09 87
96 : Int

Ett typalias för en posttyp definierar en konstruktorfunktion med ett argument för varje fält i deklarationsordning.

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

Varje posttyp alias har sin egen fältordning även för en kompatibel typ.

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

Förbättra typsäkerhet med nya typer

Aliasingtyper minskar pannplattan och förbättrar läsbarheten, men den är inte mer typsäker än den alias typ som är. Tänk på följande:

type alias Email = String

type alias Name = String

someEmail = "[email protected]"

someName = "Benedict"

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

Med hjälp av koden ovan kan vi skriva sendEmail someName , och den kommer att kompilera, även om den egentligen inte borde, för trots att namn och e-postmeddelanden som båda är String är de helt olika saker.

Vi kan verkligen skilja en String från en annan String på typnivå genom att skapa en ny typ . Här är ett exempel som skriver om Email som en type snarare än ett 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) = ...

Vår isValid funktion gör något för att avgöra om en sträng är en giltig e-postadress. create funktionen kontrollerar om en given String är ett giltigt e-postmeddelande, returnerar en Maybe inlindad Email att se till att vi bara returnerar validerade adresser. Medan vi kan undvika valideringskontrollen genom att konstruera ett Email EmailAddress "somestring" direkt genom att skriva EmailAddress "somestring" , om vår moduldeklaration inte avslöjar EmailAddress konstruktören, som visas här

module Email exposing (Email, create, send)

då har ingen annan modul åtkomst till EmailAddress konstruktören, även om de fortfarande kan använda Email posttypen i kommentarer. Det enda sättet att bygga ett nytt Email utanför den här modulen är att använda den create funktionen den tillhandahåller, och den funktionen säkerställer att den bara returnerar giltiga e-postadresser i första hand. Därför guidar detta API automatiskt användaren ner rätt sökväg via dess typsäkerhet: send bara fungerar med värden konstruerade av create , som utför en validering och tvingar hanteringen av ogiltiga e-postmeddelanden eftersom det returnerar en Maybe Email .

Om du vill exportera Email postkonstruktören kan du skriva

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

Nu kan alla filer som importerar Email också importera sin konstruktör. I det här fallet gör det möjligt för användare att kontrollera valideringen och send ogiltiga e-postmeddelanden, men du bygger inte alltid ett API som det här, så att exportera konstruktörer kan vara användbara. Med en typ som har flera konstruktörer kanske du bara vill exportera några av dem.

Konstruera typer

De type alias sökords kombination ger ett nytt namn för en typ, men type sökord i isolering förklarar en ny typ. Låt oss undersöka en av de mest grundläggande av dessa typer: Maybe

type Maybe a
    = Just a
    | Nothing

Det första att notera är att Maybe typen deklareras med en typvariabel av a . Det andra att notera är rörkaraktären, | , vilket betyder "eller". Med andra ord, något av typen Maybe a är antingen Just a eller Nothing .

När du skriver koden ovan kommer Just and Nothing till räckvidd som värdekonstruktörer och Maybe kommer det att tillgå som en typkonstruktör . Dessa är deras signaturer:

Just : a -> Maybe a

Nothing : Maybe a

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

På grund av typvariabeln a kan valfri typ "insvevas inuti" av typen Maybe . Så Maybe Int , Maybe (List String) eller Maybe (Maybe (List Html)) , är alla giltiga typer. När destrukturering någon type värde med en case uttryck, måste du ta hänsyn till varje möjlig instansiering av den typen. När det gäller ett värde av typen Maybe a , måste du redovisa både Just a case och Nothing saken:

thing : Maybe Int
thing = 
    Just 3

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

        Nothing ->
            42

-- blah = 3

Försök att skriva ovanstående kod utan Nothing klausul i case uttryck: det kommer inte att kompilera. Det är detta som gör den Maybe typkonstruktören till ett fantastiskt mönster för att uttrycka värden som kanske inte existerar, eftersom det tvingar dig att hantera logiken när värdet är Nothing .

Skriv aldrig

Typen Never kan inte konstrueras (Modulen Basics har inte exporterat sin värdekonstruktör och har inte gett dig någon annan funktion som returnerar Never heller). Det finns inget värde never : Never eller en funktion createNever : ?? -> Never .

Detta har dess fördelar: du kan koda i ett typsystem en möjlighet som inte kan hända. Detta kan ses i typer som Task Never Int som garanterar att det lyckas med ett Int ; eller Program Never som inte tar några parametrar när Elm-koden initieras från JavaScript.

Variabler av specialtyp

Elm definierar följande variabler av specialtyp som har en särskild betydelse för kompilatorn:

  • comparable : Består av Int , Float , Char , String och tuples därav. Detta tillåter användning av < och > operatörerna.

    Exempel: Du kan definiera en funktion för att hitta de minsta och största elementen i en lista ( extent ). Du tänker vilken typsignatur du ska skriva. Å ena sidan kan du skriva extentInt : List Int -> Maybe (Int, Int) och extentChar : List Char -> Maybe (Char, Char) och en annan för Float and String . Implementeringen av dessa skulle vara densamma:

    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
    

    Du kanske frestas att helt enkelt skriva ut extent : List a -> Maybe (a, a) , men kompilatorn låter dig inte göra det, eftersom funktionerna min och max inte är definierade för dessa typer (OBS: det är bara enkla omslag runt < operatören som nämns ovan). Du kan lösa detta genom att definiera extent : List comparable -> Maybe (comparable, comparable) . Detta gör att din lösning är polymorf , vilket bara betyder att den fungerar för mer än en typ.

  • number : Består av Int och Float . Tillåter användning av aritmetiska operatörer utom division. Du kan sedan definiera till exempel sum : List number -> number och få det att fungera för både ints och floats.

  • appendable : Består av String , List . Tillåter användning av operatören ++ .

  • compappend : Detta visas ibland, men är en implementeringsdetalj av kompilatorn. För närvarande kan detta inte användas i dina egna program, men nämns ibland.

Observera att i en typanteckning som denna: number -> number -> number hänvisar alla till samma typ, så att det går att skriva in Int -> Float -> Int är ett typfel. Du kan lösa detta genom att lägga till ett suffix till typvariabelns namn: number -> number' -> number'' skulle då kompilera bra.

Det finns inget officiellt namn för dessa, de kallas ibland:

  • Variabler av specialtyp
  • Typklassliknande typvariabler
  • Pseudo typeclasses

Detta beror på att de fungerar som Haskells typklasser , men utan användarens möjlighet att definiera dessa.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow