Ricerca…


Osservazioni

Per favore, gioca con questi concetti per padroneggiarli davvero! L' elm-repl (vedere l' introduzione al REPL ) è probabilmente un buon posto per giocare con il codice sopra. Puoi anche giocare con elm-repl online .

Tipi di dati comparabili

I tipi comparabili sono tipi primitivi che possono essere confrontati usando operatori di confronto dal modulo Basics , come: (<) , (>) , (<=) , (>=) , max , min , compare

I tipi comparabili in Elm sono Int , Float , Time , Char , String e tuple o liste di tipi comparabili.

Nella documentazione o nella definizione dei tipi vengono indicati come variabili di tipo speciale comparable , ad es. vedere la definizione del tipo per la funzione Basics.max :

max : comparable -> comparable -> comparable

Digita le firme

In Elm, i valori vengono dichiarati scrivendo un nome, un segno di uguale e quindi il valore effettivo:

someValue = 42

Le funzioni sono anche valori, con l'aggiunta di prendere un valore o valori come argomenti. Di solito sono scritti come segue:

double n = n * 2

Ogni valore in Elm ha un tipo. I tipi di valori sopra indicati verranno dedotti dal compilatore in base a come vengono utilizzati. Tuttavia, è consigliabile dichiarare sempre esplicitamente il tipo di qualsiasi valore di livello superiore e, per farlo, scrivere una firma del tipo come segue:

someValue : Int
someValue = 
    42

someOtherValue : Float
someOtherValue =
    42

Come possiamo vedere, 42 può essere definita come un Int o Float . Questo ha un senso intuitivo, ma vedi Tipo Variabili per maggiori informazioni.

Le firme di tipo sono particolarmente preziose se usate con le funzioni. Ecco la funzione di raddoppio di prima:

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

Questa volta, la firma ha una -> , una freccia e pronunciamo la firma come "int to int" o "prende un numero intero e restituisce un numero intero". -> indica che dando un valore double come argomento a Int , double restituirà un Int . Quindi, prende un intero per un numero intero:

> double
<function> : Int -> Int

> double 3
6 : Int

Tipi di base

In elm-repl , digita un pezzo di codice per ottenere il suo valore e il tipo dedotto. Prova quanto segue per conoscere i vari tipi esistenti:

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

> ()
() : ()

{} è il tipo di Record vuoto e () è il tipo di Tupla vuoto. Quest'ultimo è spesso usato ai fini della valutazione pigra. Vedere l'esempio corrispondente in Funzioni e Applicazione parziale .

Nota come il number appare nonapitalizzato. Ciò indica che è una variabile di tipo , e inoltre il particolare number parole si riferisce a una variabile di tipo speciale che può essere sia Int sia Float (vedere le sezioni corrispondenti per ulteriori informazioni). I tipi sono sempre in maiuscolo, ad esempio Char , Float , List String , eccetera.

Digita le variabili

Le variabili di tipo sono nomi non specializzati nelle firme dei tipi. A differenza delle loro controparti in maiuscolo, come Int e String , non rappresentano un singolo tipo, ma piuttosto un qualsiasi tipo. Sono utilizzati per scrivere funzioni generiche che possono funzionare su qualsiasi tipo o tipo e sono particolarmente utili per scrivere operazioni su contenitori come List o Dict . La funzione List.reverse , ad esempio, ha la seguente firma:

reverse : List a -> List a

Il che significa che può funzionare su un elenco di qualsiasi valore di tipo , quindi List Int , List (List String) , entrambi quelli e tutti gli altri possono essere reversed lo stesso. Quindi, a è una variabile di tipo che può essere valida per qualsiasi tipo.

La funzione reverse avrebbe potuto utilizzare qualsiasi nome di variabile incapitalizzata nella sua firma del tipo, ad eccezione della manciata di nomi di variabili di tipo speciale , come il number (vedere l'esempio corrispondente su quello per ulteriori informazioni):

reverse : List lol -> List lol

reverse : List wakaFlaka -> List wakaFlaka

I nomi delle variabili di tipo diventano significativi solo quando ci sono variabili di tipo diverse all'interno di una singola firma, esemplificate dalla funzione map sugli elenchi:

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

map prende qualche funzione da qualsiasi tipo a a qualsiasi tipo b , insieme ad una lista con elementi di qualche tipo a , e restituisce una lista di elementi di qualche tipo b , che ottiene applicando la funzione data ad ogni elemento della lista.

Facciamo la firma concreta per vedere meglio questo:

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

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

Come possiamo vedere, sia a = Int che b = Int in questo caso. Ma, se la map avesse una map tipo firma come map : (a -> a) -> List a -> List a , allora funzionerebbe solo su funzioni che operano su un singolo tipo e non saresti mai in grado di cambiare il tipo di una lista usando la funzione map . Ma poiché la firma del tipo di map ha più variabili di tipo diverso, a e b , possiamo usare la map per cambiare il tipo di una lista:

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

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

In questo caso, a = Int e b = Bool . Quindi, per essere in grado di utilizzare funzioni che possono assumere e restituire tipi diversi , è necessario utilizzare variabili di tipo diverse.

Digita alias

A volte vogliamo dare a un tipo un nome più descrittivo. Supponiamo che la nostra app abbia un tipo di dati che rappresenta gli utenti:

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

E le nostre funzioni sugli utenti hanno le firme del tipo sulla falsariga di:

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

Questo potrebbe diventare piuttosto ingombrante con un tipo di record più grande per un utente, quindi usiamo un alias di tipo per ridurre le dimensioni e dare un nome più significativo a quella struttura di dati:

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


prettyPrintUser : User -> String

Gli alias di tipo rendono molto più pulito definire e utilizzare un modello per un'applicazione:

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

Usando l' type alias letteralmente si type alias solo un tipo con il nome che gli si dà. L'uso del tipo di Model sopra è esattamente lo stesso di usare { count : Int, lastEditMade : Time } . Ecco un esempio che mostra come gli alias non sono diversi dai tipi sottostanti:

type alias Bugatti = Int

type alias Fugazi = Int

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

> unstoppableForceImmovableObject 09 87
96 : Int

Un alias di tipo per un tipo di record definisce una funzione di costruzione con un argomento per ogni campo nell'ordine di dichiarazione.

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

Ogni alias del tipo di record ha il proprio ordine di campo anche per un tipo compatibile.

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

Migliorare la sicurezza del tipo usando nuovi tipi

I tipi di aliasing riducono lo standard di stampa e migliorano la leggibilità, ma non è più sicuro dal punto di vista del tipo aliasato. Considera quanto segue:

type alias Email = String

type alias Name = String

someEmail = "[email protected]"

someName = "Benedict"

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

Usando il codice sopra, possiamo scrivere sendEmail someName , e verrà compilato, anche se in realtà non dovrebbe, perché nonostante i nomi e le email siano entrambi String s, sono cose completamente diverse.

Possiamo veramente distinguere una String da un'altra String a livello di carattere creando un nuovo tipo . Ecco un esempio che riscrive l' Email come type piuttosto che come 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) = ...

La isValid funzione isValid fa qualcosa per determinare se una stringa è un indirizzo email valido. Il create funzione controlla se una data String è un indirizzo email valido, restituendo una Maybe -wrapped Email per garantire che torniamo solo indirizzi convalidati. Mentre possiamo aggirare il controllo di validazione costruendo Email - Email direttamente scrivendo EmailAddress "somestring" , se la nostra dichiarazione del modulo non espone il costruttore EmailAddress , come mostrato qui

module Email exposing (Email, create, send)

quindi nessun altro modulo avrà accesso al costruttore EmailAddress , sebbene possano comunque utilizzare il tipo di Email nelle annotazioni. L' unico modo per creare una nuova e Email al di fuori di questo modulo è utilizzando la funzione di create che fornisce, e tale funzione garantisce che restituirà solo indirizzi email validi in primo luogo. Quindi, questa API guida automaticamente l'utente lungo il percorso corretto tramite il suo tipo di sicurezza: send solo lavori con valori costruiti da create , che esegue una convalida e impone la gestione di e-mail non valide poiché restituisce un messaggio di Maybe Email .

Se desideri esportare la funzione di costruzione Email , puoi scrivere

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

Ora qualsiasi file che importa Email può anche importare il suo costruttore. In questo caso, fare ciò consentirebbe agli utenti di aggirare la convalida e send e-mail non valide, ma non si sta sempre costruendo un'API come questa, quindi i costruttori di esportazione possono essere utili. Con un tipo con diversi costruttori, potresti anche voler esportarne solo alcuni.

Costruire tipi

La combinazione di parole chiave type alias dà un nuovo nome per un tipo, ma la parola chiave type in isolamento dichiara un nuovo tipo. Esaminiamo uno dei più fondamentali di questi tipi: Maybe

type Maybe a
    = Just a
    | Nothing

La prima cosa da notare è che il tipo Maybe è dichiarato con una variabile di tipo di a . La seconda cosa da notare è il carattere pipe, | , che significa "o". In altre parole, qualcosa di tipo Maybe a o è Just a o Nothing .

Quando scrivi il codice di cui sopra, Just e Nothing entrano in campo come costruttori di valori e Maybe entra in ambito come costruttore di tipi . Queste sono le loro firme:

Just : a -> Maybe a

Nothing : Maybe a

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

A causa della variabile di tipo a , qualsiasi tipo può essere "incluso all'interno" del tipo Maybe . Quindi, Maybe Int , Maybe (List String) , o Maybe (Maybe (List Html)) , sono tutti tipi validi. Quando si destrutturano qualsiasi valore di type con un'espressione di case , è necessario tenere conto di ogni possibile istanziazione di quel tipo. Nel caso di un valore di tipo Maybe a , devi tenere conto sia del caso Just a caso Nothing :

thing : Maybe Int
thing = 
    Just 3

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

        Nothing ->
            42

-- blah = 3

Prova a scrivere il codice sopra senza la clausola Nothing nell'espressione case : non verrà compilato. Questo è ciò che rende il costruttore di tipo Maybe un ottimo pattern per esprimere valori che potrebbero non esistere, poiché ti costringe a gestire la logica di quando il valore è Nothing .

Il tipo Never

Il tipo Never non può essere costruito (il modulo Basics non ha esportato il suo costruttore di valori e non ha fornito alcuna altra funzione che restituisca Never ). Non c'è never : Never un valore never : Never o una funzione createNever : ?? -> Never .

Questo ha i suoi vantaggi: puoi codificare in un sistema di tipi una possibilità che non può accadere. Questo può essere visto in tipi come Task Never Int che garantisce che avrà successo con un Int ; o Program Never che non prenderà alcun parametro durante l'inizializzazione del codice Elm da JavaScript.

Variabili di tipo speciale

Elm definisce le seguenti variabili di tipo speciali che hanno un significato particolare per il compilatore:

  • comparable : Compreso di Int , Float , Char , String e tuple degli stessi. Questo consente l'uso degli operatori < e > .

    Esempio: è possibile definire una funzione per trovare gli elementi più piccoli e più grandi in una lista ( extent ). Pensi che tipo di firma scrivere. Da un lato, è possibile scrivere extentInt : List Int -> Maybe (Int, Int) e extentChar : List Char -> Maybe (Char, Char) e un altro per Float e String . L'implementazione di questi sarebbe la stessa:

    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
    

    Potresti essere tentato di scrivere semplicemente extent : List a -> Maybe (a, a) , ma il compilatore non ti permetterà di farlo, perché le funzioni min e max non sono definite per questi tipi (NB: questi sono solo semplici wrapper attorno all'operatore < cui sopra). Puoi risolvere questo problema definendo l' extent : List comparable -> Maybe (comparable, comparable) . Ciò consente alla soluzione di essere polimorfica , il che significa che funzionerà per più di un tipo.

  • number : Compreso di Int e Float . Permette l'uso di operatori aritmetici tranne la divisione. È quindi possibile definire ad esempio la sum : List number -> number e farlo funzionare sia per interi che per float.

  • appendable : composto da String , List . Permette l'uso dell'operatore ++ .

  • compappend : questo a volte appare, ma è un dettaglio di implementazione del compilatore. Attualmente questo non può essere usato nei tuoi programmi, ma a volte viene menzionato.

Notare che in un'annotazione di tipo come questa: number -> number -> number questi si riferiscono tutti allo stesso tipo, quindi passare in Int -> Float -> Int sarebbe un errore di tipo. Puoi risolvere questo problema aggiungendo un suffisso al nome della variabile di tipo: number -> number' -> number'' quindi compilerebbe bene.

Non esiste un nome ufficiale per questi, a volte vengono chiamati:

  • Variabili di tipo speciale
  • Variabili di tipo simili al tipo di prodotto
  • Pseudo-typeclasses

Questo perché funzionano come le classi di tipi di Haskell, ma senza la possibilità per l'utente di definirli.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow