Buscar..


Observaciones

¡Por favor juega con estos conceptos para dominarlos realmente! elm-repl (ver la Introducción al REPL ) es probablemente un buen lugar para jugar con el código anterior. También puedes jugar con elm-repl online .

Tipos de datos comparables

Los tipos comparables son tipos primitivos que pueden compararse utilizando operadores de comparación del módulo de Conceptos básicos , como: (<) , (>) , (<=) , (>=) , max , min , compare

Los tipos comparables en Elm son Int , Float , Time , Char , String y tuplas o listas de tipos comparables.

En la documentación o en las definiciones de tipo se les conoce como una variable de tipo especial comparable , por ejemplo. ver definición de tipo para la función Basics.max :

max : comparable -> comparable -> comparable

Tipo de firmas

En Elm, los valores se declaran escribiendo un nombre, un signo igual y luego el valor real:

someValue = 42

Las funciones también son valores, con la adición de tomar un valor o valores como argumentos. Por lo general se escriben de la siguiente manera:

double n = n * 2

Cada valor en Elm tiene un tipo. Los tipos de los valores anteriores se infiere por el compilador dependiendo de cómo se utilizan. Pero es una buena práctica siempre declarar explícitamente el tipo de cualquier valor de nivel superior, y para hacerlo, escriba una firma de tipo de la siguiente manera:

someValue : Int
someValue = 
    42

someOtherValue : Float
someOtherValue =
    42

Como podemos ver, 42 se puede definir como o bien un Int o un Float . Esto tiene sentido intuitivo, pero consulte Variables de tipo para obtener más información.

Las firmas de tipos son particularmente valiosas cuando se usan con funciones. Aquí está la función de duplicación de antes:

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

Esta vez, la firma tiene una -> , una flecha, y la pronunciaríamos como "int to int", o "toma un entero y devuelve un entero". -> indica que al dar un valor Int double como argumento, double devolverá un Int . Por lo tanto, toma un entero a un entero:

> double
<function> : Int -> Int

> double 3
6 : Int

Tipos basicos

En elm-repl , escriba un fragmento de código para obtener su valor y tipo inferido. Intente lo siguiente para aprender sobre los diversos tipos que existen:

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

> ()
() : ()

{} es el tipo de registro vacío, y () es el tipo de tupla vacío. Este último se utiliza a menudo para fines de evaluación perezosa. Vea el ejemplo correspondiente en Funciones y Aplicación Parcial .

Observe cómo el number aparece sin capitalizar. Esto indica que es una Variable de tipo y, además, el number palabra en particular se refiere a una Variable de tipo especial que puede ser Int o Float (consulte las secciones correspondientes para obtener más información). Sin embargo, los tipos siempre están en mayúsculas, como Char , Float , List String , etcétera.

Variables de tipo

Las variables de tipo son nombres sin capitalizar en las firmas de tipo. A diferencia de sus homólogos en mayúsculas, como Int y String , no representan un solo tipo, sino cualquier tipo. Se utilizan para escribir funciones genéricas que pueden operar en cualquier tipo o tipo, y son particularmente útiles para escribir operaciones en contenedores como List o Dict . La función List.reverse , por ejemplo, tiene la siguiente firma:

reverse : List a -> List a

Lo que significa que puede funcionar en una lista de cualquier valor de tipo , por lo que List Int , List (List String) , ambos y cualquier otro puede reversed la misma manera. Por lo tanto, a es una variable de tipo que puede representar cualquier tipo.

La función reverse podría haber usado cualquier nombre de variable sin capitalizar en su firma de tipo, excepto por un puñado de nombres de variables de tipo especial , como el number (consulte el ejemplo correspondiente en este enlace para obtener más información):

reverse : List lol -> List lol

reverse : List wakaFlaka -> List wakaFlaka

Los nombres de las variables de tipo se vuelven significativos solo cuando hay diferentes variables de tipo dentro de una única firma, ejemplificada por la función de map en las listas:

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

map toma alguna función de cualquier tipo a a cualquier tipo b , junto con una lista con los elementos de algún tipo a , y devuelve una lista de elementos de algún tipo b , que se pone mediante la aplicación de la función dada a cada elemento de la lista.

Hagamos la firma concreta para ver mejor esto:

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

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

Como podemos ver, tanto a = Int como b = Int en este caso. Pero, si el map tuviera una firma de tipo como map : (a -> a) -> List a -> List a , entonces solo funcionaría en funciones que operan en un solo tipo, y nunca sería capaz de cambiar el tipo de una lista mediante la función de map . Pero dado que el tipo de firma del map tiene múltiples variables de tipo diferente, a y b , podemos usar map para cambiar el tipo de una lista:

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

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

En este caso, a = Int y b = Bool . Por lo tanto, para poder usar funciones que pueden tomar y devolver diferentes tipos, debe usar diferentes variables de tipo.

Tipo de alias

A veces queremos darle a un tipo un nombre más descriptivo. Digamos que nuestra aplicación tiene un tipo de datos que representa a los usuarios:

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

Y nuestras funciones en los usuarios tienen firmas de tipo en la línea de:

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

Esto podría volverse bastante difícil de manejar con un tipo de registro más grande para un usuario, así que usemos un alias de tipo para reducir el tamaño y darle un nombre más significativo a esa estructura de datos:

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


prettyPrintUser : User -> String

Los alias de tipo hacen que sea mucho más limpio definir y usar un modelo para una aplicación:

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

Usar el type alias literalmente solo alias un tipo con el nombre que le das. Usar el tipo de Model anterior es exactamente lo mismo que usar { count : Int, lastEditMade : Time } . Aquí hay un ejemplo que muestra cómo los alias no son diferentes a los tipos subyacentes:

type alias Bugatti = Int

type alias Fugazi = Int

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

> unstoppableForceImmovableObject 09 87
96 : Int

Un alias de tipo para un tipo de registro define una función constructora con un argumento para cada campo en el orden de declaración.

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

Cada alias de tipo de registro tiene su propio orden de campo incluso para un tipo compatible.

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

Mejora de la seguridad de tipos utilizando nuevos tipos

Los tipos de alias reducen la repetición y mejoran la legibilidad, pero no son más seguros de lo que son los tipos con alias. Considera lo siguiente:

type alias Email = String

type alias Name = String

someEmail = "[email protected]"

someName = "Benedict"

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

Usando el código anterior, podemos escribir sendEmail someName , y se compilará, aunque realmente no debería, porque a pesar de que los nombres y los correos electrónicos son String , son cosas completamente diferentes.

Podemos distinguir realmente una String de otra String en el nivel de tipo creando un nuevo tipo . Aquí hay un ejemplo que reescribe el Email como un type lugar de un 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) = ...

Nuestra función isValid hace algo para determinar si una cadena es una dirección de correo electrónico válida. El create función comprueba si un determinado String es un correo electrónico válido, devolviendo un Maybe -wrapped Email para asegurarse de que sólo volvemos direcciones validadas. Si bien podemos esquivar la verificación de validación construyendo un Email directamente escribiendo EmailAddress "somestring" , si nuestra declaración de módulo no expone el constructor EmailAddress , como se muestra aquí

module Email exposing (Email, create, send)

entonces ningún otro módulo tendrá acceso al constructor EmailAddress , aunque aún pueden usar el tipo de Email en las anotaciones. La única forma de crear un nuevo Email fuera de este módulo es mediante la función de create que proporciona, y esa función garantiza que solo devolverá direcciones de correo electrónico válidas en primer lugar. Por lo tanto, esta API guía automáticamente al usuario por el camino correcto a través de su seguridad de tipo: send solo funciona con los valores construidos por create , que realiza una validación, e impone el manejo de correos electrónicos no válidos, ya que devuelve un Maybe Email .

Si desea exportar el constructor de Email , puede escribir

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

Ahora cualquier archivo que importe Email también puede importar su constructor. En este caso, hacerlo permitiría a los usuarios eludir la validación y send correos electrónicos no válidos, pero no siempre está creando una API como esta, por lo que exportar constructores puede ser útil. Con un tipo que tiene varios constructores, también es posible que solo desee exportar algunos de ellos.

Construyendo tipos

La combinación de palabras clave de type alias da un nuevo nombre para un tipo, pero la palabra clave de type en aislamiento declara un nuevo tipo. Examinemos uno de los más fundamentales de estos tipos: Maybe

type Maybe a
    = Just a
    | Nothing

Lo primero que se debe tener en cuenta es que el tipo Maybe se declara con una variable de tipo de a . La segunda cosa a tener en cuenta es el carácter de la tubería, | , que significa "o". En otras palabras, algo de tipo Maybe a sea Just a or Nothing .

Cuando escribe el código anterior, Just and Nothing entra en el alcance como constructores de valor , y Maybe entra en el alcance como un constructor de tipo . Estas son sus firmas:

Just : a -> Maybe a

Nothing : Maybe a

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

Debido a la variable de tipo a , cualquier tipo puede "ajustarse dentro" del tipo Maybe . Entonces, Maybe Int , Maybe (List String) , o Maybe (Maybe (List Html)) , son todos tipos válidos. Al desestructurar cualquier valor de type con una expresión de case , debe tener en cuenta cada posible instanciación de ese tipo. En el caso de un valor de tipo Maybe a , debe tener en cuenta tanto el caso Just a case como el caso Nothing :

thing : Maybe Int
thing = 
    Just 3

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

        Nothing ->
            42

-- blah = 3

Intente escribir el código anterior sin la cláusula Nothing en la expresión del case : no se compilará. Esto es lo que hace que el constructor de tipo Maybe un gran patrón para expresar valores que pueden no existir, ya que te obliga a manejar la lógica de cuando el valor es Nothing .

El nunca escribe

El tipo Never se puede construir (el módulo Basics no ha exportado su constructor de valores y tampoco le ha dado ninguna otra función que devuelva Never ). No hay ningún valor never : Never o una función createNever : ?? -> Never .

Esto tiene sus beneficios: puede codificar en un sistema de tipos una posibilidad que no puede ocurrir. Esto se puede ver en tipos como Task Never Int que garantiza que tendrá éxito con un Int ; o Program Never eso no tomará ningún parámetro al inicializar el código Elm desde JavaScript.

Variables Tipo Especial

Elm define las siguientes variables de tipo especial que tienen un significado particular para el compilador:

  • comparable : Compuesto por Int , Float , Char , String y tuples. Esto permite el uso de los operadores < y > .

    Ejemplo: Podría definir una función para encontrar los elementos más pequeños y más grandes en una lista ( extent ). Piensas qué tipo de firma escribir. Por un lado, puede escribir extentInt : List Int -> Maybe (Int, Int) y extentChar : List Char -> Maybe (Char, Char) y otro para Float and String . La implementación de estos sería la misma:

    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
    

    Es posible que tenga la tentación de simplemente escribir la extent : List a -> Maybe (a, a) , pero el compilador no le permitirá hacer esto, porque las funciones min y max no están definidas para estos tipos (NB: estos son solo envoltorios simples alrededor del < operador mencionado anteriormente). Puede resolver esto definiendo la extent : List comparable -> Maybe (comparable, comparable) . Esto permite que su solución sea polimórfica , lo que significa que funcionará para más de un tipo.

  • number : Compuesto por Int y Float . Permite el uso de operadores aritméticos excepto división. Luego puede definir, por ejemplo, la sum : List number -> number y hacer que funcione tanto para ints como para flotadores.

  • appendable : compuesto por String , List . Permite el uso del operador ++ .

  • compappend : esto aparece a veces, pero es un detalle de implementación del compilador. Actualmente esto no se puede utilizar en sus propios programas, pero a veces se menciona.

Tenga en cuenta que en una anotación de tipo como esta: number -> number -> number todos se refieren al mismo tipo, por lo que pasar Int -> Float -> Int sería un error de tipo. Puede resolver esto agregando un sufijo al tipo variable nombre: number -> number' -> number'' luego compilaría bien.

No hay un nombre oficial para estos, a veces se les llama:

  • Variables Tipo Especial
  • Variables de tipo tipo clase de clase
  • Clases de seudo tipo

Esto se debe a que funcionan como las Clases de tipos de Haskell, pero sin que el usuario pueda definirlas.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow