Diferencia en Elm entre tipo y alias de tipo?


En Elm, no puedo averiguar cuándo type es la palabra clave apropiada vs. type alias. La documentación no parece tener una explicación de esto, ni puedo encontrar una en las notas de la versión. ¿Está documentado en alguna parte?

 81
elm
Author: ehdv, 2015-08-24

4 answers

Cómo lo pienso: {[19]]}

type se utiliza para definir nuevos tipos de unión:

type Thing = Something | SomethingElse

Antes de esta definición Something y SomethingElse no significaba nada. Ahora ambos son de tipo Thing, que acabamos de definir.

type alias se utiliza para dar un nombre a algún otro tipo que ya existe:

type alias Location = { lat:Int, long:Int }

{ lat = 5, long = 10 } tiene el tipo { lat:Int, long:Int }, que ya era un tipo válido. Pero ahora también podemos decir que tiene el tipo Location porque es un alias para el mismo tipo.

Vale la pena señalar que lo siguiente compilará muy bien y mostrará "thing". A pesar de que especificamos thing es un String y aliasedStringIdentity toma un AliasedString, no obtendremos un error de que hay un desajuste de tipo entre 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
 123
Author: robertjlooby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-29 16:35:01

La clave es La palabra alias. En el curso de la programación, cuando quieres agrupar cosas que pertenecen juntas, lo pones en un registro, como en el caso de un punto

{ x = 5, y = 4 }  

O un registro del estudiante.

{ name = "Billy Bob", grade = 10, classof = 1998 }

Ahora, si usted necesita pasar estos registros alrededor, usted tendría que deletrear todo el tipo, como:

add : { x:Int, y:Int } -> { x:Int, y:Int } -> { x:Int, y:Int }
add a b =
  { a.x + b.x, a.y + b.y }

Si pudiera alias un punto, la firma sería mucho más fácil de escribir!

type alias Point = { x:Int, y:Int }
add : Point -> Point -> Point
add a b =
  { a.x + b.x, a.y + b.y }

Así que un alias es una abreviatura de otra cosa. Aqui, es una taquigrafía para un tipo de disco. Usted puede pensar en ello como dar un nombre a un tipo de registro que va a utilizar a menudo. Es por eso que se llama un alias's es otro nombre para el tipo de registro desnudo que está representado por { x:Int, y:Int }

, Mientras que type resuelve un problema diferente. Si vienes de OOP, es el problema que resuelves con la herencia,la sobrecarga del operador, etc.-- a veces, quieres tratar los datos como algo genérico, y a veces quieres tratarlos como algo específico cosa.

Un lugar común donde esto sucede es cuando se pasan mensajes around como el sistema postal. Cuando envías una carta, quieres que el sistema postal trate todos los mensajes como la misma cosa, por lo que solo tienes que diseñar el sistema postal una vez. Y además, el trabajo de enrutar el mensaje debe ser independiente del mensaje contenido dentro. Solo cuando la carta llega a su destino te importa cuál es el mensaje.

De la misma manera, podríamos definir un type como una unión de todos los diferentes tipos de mensajes que podrían suceder. Digamos que estamos implementando un sistema de mensajería entre estudiantes universitarios a sus padres. Así que solo hay dos mensajes que los universitarios pueden enviar: 'Necesito dinero para cerveza' y 'Necesito calzoncillos'.

type MessageHome = NeedBeerMoney | NeedUnderpants

Así que ahora, cuando diseñamos el sistema de enrutamiento, los tipos de nuestras funciones solo pueden pasar MessageHome, en lugar de preocuparse por todos los diferentes tipos de mensajes que podría ser. Al sistema de enrutamiento no le importa. Sólo necesita sepa que es un MessageHome. Es solo cuando el mensaje llega a su destino, la casa de los padres, que necesitas averiguar qué es.

case message of
  NeedBeerMoney ->
    sayNo()
  NeedUnderpants ->
    sendUnderpants(3)

Si conoce la arquitectura Elm, la función update es una sentencia case gigante, porque ese es el destino de donde se enruta el mensaje y, por lo tanto, se procesa. Y usamos los tipos de unión para tener un solo tipo con el que lidiar al pasar el mensaje, pero luego podemos usar una sentencia case para desentrañar exactamente qué mensaje fue, para que podamos acéptalo.

 6
Author: Wilhelm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-13 03:24:10

La principal diferencia, como yo lo veo, es si type checker le gritará si usa el tipo "synomical".

Cree el siguiente archivo, colóquelo en algún lugar y ejecute elm-reactor, luego vaya a http://localhost:8000 para ver la diferencia:

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

Si descomentas 2. y comentas 1. verás:

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
 1
Author: EugZol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-07 07:09:44

Permítanme complementar las respuestas anteriores centrándose en los casos de uso y proporcionando un poco de contexto sobre las funciones y módulos del constructor.



Usos de type alias

  1. Crear un alias y una función constructora para un registro
    Este es el caso de uso más común: puede definir un nombre alternativo y una función de constructor para un tipo particular de formato de registro.

    type alias Person =
        { name : String
        , age : Int
        }
    

    Definir el alias de tipo automáticamente implica la siguiente función constructora (pseudo código):
    Person : String -> Int -> { name : String, age : Int }
    Esto puede ser útil, por ejemplo, cuando desea escribir un decodificador Json.

    personDecoder : Json.Decode.Decoder Person
    personDecoder =
        Json.Decode.map2 Person
            (Json.Decode.field "name" Json.Decode.String)
            (Json.Decode.field "age" Int)
    


  2. Especifique los campos obligatorios
    A veces lo llaman "registros extensibles", lo que puede ser engañoso. Esta sintaxis se puede usar para especificar que está esperando algún registro con campos particulares presentes. Tales como:

    type alias NamedThing x =
        { x
            | name : String
        }
    
    showName : NamedThing x -> Html msg
    showName thing =
        Html.text thing.name
    

    Entonces puede usar la función anterior de esta manera (para ejemplo en su vista):

    let
        joe = { name = "Joe", age = 34 }
    in
        showName joe
    

    La charla de Richard Feldman sobre ElmEurope 2017 puede proporcionar más información sobre cuándo vale la pena usar este estilo.

  3. Cambiar el nombre de cosas
    Usted podría hacer esto, porque los nuevos nombres podrían proporcionar un significado adicional más adelante en su código, como en este ejemplo

    type alias Id = String
    
    type alias ElapsedTime = Time
    
    type SessionStatus
        = NotStarted
        | Active Id ElapsedTime
        | Finished Id
    

    Quizás un mejor ejemplo de este tipo de uso en core es Time.

  4. Volver a exponer un tipo desde un módulo diferente
    Si está escribiendo un paquete (no una aplicación), es posible que necesite implementar un tipo en un módulo, tal vez en un módulo interno (no expuesto), pero desea exponer el tipo desde un módulo diferente (público). O, alternativamente, desea exponer su tipo desde varios módulos.
    Task en core y Http.Petición en Http son ejemplos para la primera, mientras que the Json.Codificar.Valor y Json.Decodificar.El par Value es un ejemplo del último.

    Solo puede hacer esto cuando desea mantener el tipo opaco: no expone las funciones del constructor. Para obtener más detalles, consulte los usos de type a continuación.

Vale la pena notar que en los ejemplos anteriores solo #1 proporciona una función de constructor. Si expone su alias de tipo en # 1 como module Data exposing (Person) eso expondrá el nombre del tipo, así como el constructor función.



Usos de type

  1. Definir un tipo de unión etiquetado
    Este es el caso de uso más común, un buen ejemplo de ello es el Maybe escriba el núcleo :

    type Maybe a
        = Just a
        | Nothing
    

    Cuando define un tipo, también define sus funciones constructoras. En caso de Tal vez estos son (pseudo-código):

    Just : a -> Maybe a
    
    Nothing : Maybe a
    

    Lo que significa que si declaras este valor:

    mayHaveANumber : Maybe Int
    

    Puede crearlo por cualquiera de los dos

    mayHaveANumber = Nothing
    

    O

    mayHaveANumber = Just 5
    

    Las etiquetas Just y Nothing no solo sirven como funciones constructoras, también sirven como destructores o patrones en una expresión case. Lo que significa que usando estos patrones se puede ver dentro de un Maybe:

    showValue : Maybe Int -> Html msg
    showValue mayHaveANumber =
        case mayHaveANumber of
            Nothing ->
                Html.text "N/A"
    
            Just number ->
                Html.text (toString number)
    

    Puedes hacer esto, porque el módulo Maybe está definido como

    module Maybe exposing 
        ( Maybe(Just,Nothing)
    

    También podría decir

    module Maybe exposing 
        ( Maybe(..)
    

    Las dos son equivalentes en este caso, pero ser explícito se considera una virtud en Elm, especialmente cuando se escribiendo un paquete.


  1. Ocultar detalles de implementación
    Como se señaló anteriormente, es una elección deliberada que las funciones constructoras de Maybe sean visibles para otros módulos.

    Hay otros casos, sin embargo, cuando el autor decide ocultarlos. Un ejemplo de esto en core es Dict. Como consumidor del paquete, no debería poder ver los detalles de implementación del árbol Rojo/Negro algoritmo detrás de Dict y meterse con los nodos directamente. Ocultar las funciones del constructor obliga al consumidor de su módulo / paquete a crear solo valores de su tipo (y luego transformar esos valores) a través de las funciones que expone.

    Esta es la razón por la que a veces cosas como esta aparecen en el código

    type Person =
        Person { name : String, age : Int }
    

    A diferencia de la definición type alias en la parte superior de este post, esta sintaxis crea un nuevo tipo "union" con solo una función constructora, pero esa función constructora puede estar oculto de otros módulos / paquetes.

    Si el tipo se expone así:

    module Data exposing (Person)
    

    Solo el código en el módulo Data puede crear un valor de Persona y solo ese código puede coincidir con el patrón.

 1
Author: Gabor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-29 14:51:43