¿Diferencia en Elm entre tipo y alias de tipo?

93

En Elm, no puedo averiguar cuándo typees el frente de palabras clave adecuadas 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á esto documentado en alguna parte?

ehdv
fuente

Respuestas:

136

Cómo lo pienso:

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

type Thing = Something | SomethingElse

Antes de esta definición Somethingy SomethingElseno significaba nada. Ahora ambos son del tipo Thing, que acabamos de definir.

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

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

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

Vale la pena señalar que lo siguiente se compilará y mostrará sin problemas "thing". Aunque especificamos thingis a Stringy aliasedStringIdentitytoma an AliasedString, no obtendremos un error de que existe una falta de coincidencia de tipos 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
robertjlooby
fuente
No estoy seguro de su punto del último párrafo. ¿Estás tratando de decir que siguen siendo del mismo tipo sin importar el alias?
ZHANG Cheng
7
Sí, solo señalar que el compilador considera que el tipo de alias es el mismo que el original
robertjlooby
Entonces, cuando usa la {}sintaxis de registro, ¿está definiendo un nuevo tipo?
2
{ lat:Int, long:Int }no define un nuevo tipo. Ese ya es un tipo válido. type alias Location = { lat:Int, long:Int }tampoco define un nuevo tipo, solo le da otro nombre (quizás más descriptivo) a un tipo ya válido. type Location = Geo { lat:Int, long:Int }definiría un nuevo tipo ( Location)
robertjlooby
1
¿Cuándo se debe usar el tipo frente al alias de tipo? ¿Dónde está la desventaja de usar siempre el tipo?
Richard Haven
8

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 expediente de estudiante.

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

Ahora, si necesita pasar estos registros, tendrá 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 pudieras poner un alias en 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 }

Entonces, un alias es una forma abreviada de otra cosa. Aquí, es una abreviatura de un tipo de registro. Puede pensar en ello como dar un nombre a un tipo de registro que utilizará con frecuencia. Por eso se llama alias: es otro nombre para el tipo de registro desnudo que está representado por{ x:Int, y:Int }

Considerando que typeresuelve un problema diferente. Si viene de OOP, es el problema que resuelve con la herencia, la sobrecarga de operadores, etc., a veces, desea tratar los datos como algo genérico y, a veces, desea tratarlos como algo específico.

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

De la misma manera, podríamos definir a typecomo una unión de todos los diferentes tipos de mensajes que podrían ocurrir. Digamos que estamos implementando un sistema de mensajería entre estudiantes universitarios y 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 pueden simplemente pasar MessageHome, en lugar de preocuparse por todos los diferentes tipos de mensajes que podrían ser. Al sistema de enrutamiento no le importa. Solo necesita saber que es un MessageHome. Solo cuando el mensaje llega a su destino, la casa de los padres, es necesario averiguar qué es.

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

Si conoce la arquitectura Elm, la función de actualización es una declaración de caso gigante, porque ese es el destino al que se enruta el mensaje y, por lo tanto, se procesa. Y usamos tipos de unión para tener un solo tipo con el que lidiar cuando se pasa el mensaje, pero luego podemos usar una declaración de caso para averiguar exactamente qué mensaje era, para que podamos lidiar con él.

Wilhelm
fuente
5

Permítanme complementar las respuestas anteriores centrándome en casos de uso y proporcionando un pequeño contexto sobre las funciones y los 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 constructora para un tipo particular de formato de registro.

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

    Definir el alias de tipo implica automáticamente la siguiente función constructora (pseudocódigo):
    Person : String -> Int -> { name : String, age : Int }
    Esto puede resultar útil, por ejemplo, cuando se 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 resultar engañoso. Esta sintaxis se puede utilizar para especificar que está esperando algún registro con campos particulares presentes. 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 (por 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 las cosas
    Puede 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 esTime .

  4. Volver a exponer un tipo de 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 de un módulo (público) diferente. O, alternativamente, desea exponer su tipo de varios módulos.
    Tasken core y Http.Request en Http son ejemplos del primero, mientras que Json.Encode.Value y Json.Decode.Value par es un ejemplo del último.

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

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



Usos de type

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

    type Maybe a
        = Just a
        | Nothing

    Cuando define un tipo, también define sus funciones de constructor. En el caso de Maybe, estos son (pseudocódigo):

    Just : a -> Maybe a
    
    Nothing : Maybe a

    Lo que significa que si declaras este valor:

    mayHaveANumber : Maybe Int

    Puede crearlo por

    mayHaveANumber = Nothing

    o

    mayHaveANumber = Just 5

    Las etiquetas Justy Nothingno solo sirven como funciones de constructor, también sirven como destructores o patrones en una caseexpresión. Lo que significa que usando estos patrones puedes 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)

    Puede hacer esto, porque el módulo Quizás se define como

    module Maybe exposing 
        ( Maybe(Just,Nothing)

    También podría decir

    module Maybe exposing 
        ( Maybe(..)

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


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

    Sin embargo, hay otros casos en los que el autor decide ocultarlos. Un ejemplo de esto en el núcleo esDict . Como consumidor del paquete, no debería poder ver los detalles de implementación del algoritmo del árbol Rojo / Negro detrás Dicty 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 aparecen cosas como esta en el código

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

    A diferencia de la type aliasdefinición en la parte superior de esta publicación, esta sintaxis crea un nuevo tipo de "unión" con solo una función de constructor, pero esa función de constructor se puede ocultar de otros módulos / paquetes.

    Si el tipo se expone así:

    module Data exposing (Person)

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

Gabor
fuente
1

La principal diferencia, como yo lo veo, es si el verificador de tipos le gritará si usa el tipo "sinómico".

Cree el siguiente archivo, colóquelo en algún lugar y ejecútelo elm-reactor, luego vaya a http://localhost:8000para 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 descomenta 2.y comenta 1., verá:

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
EugZol
fuente
0

An aliases solo un nombre más corto para algún otro tipo, similar classen OOP. Exp:

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

Un type(sin alias) le permitirá definir su propio tipo, por lo que se puede definir como tipos Int, String... para que usted aplicación. Por ejemplo, en el caso común, se puede usar para la descripción de un estado de aplicación:

type AppState = 
  Loading          --loading state
  |Loaded          --load successful
  |Error String    --Loading error

Para que puedas manejarlo fácilmente en viewolmo:

-- VIEW
...
case appState of
    Loading -> showSpinner
    Loaded -> showSuccessData
    Error error -> showError

...

Creo que conoces la diferencia entre typey type alias.

Pero por qué y cómo usar typey type aliases importante con la elmaplicación, ustedes pueden consultar el artículo de Josh Clayton.

hien
fuente