west - ¿Diferencia en Elm entre tipo y alias de tipo?
elm wood (5)
Cómo lo pienso:
type
se usa para definir nuevos tipos de unión:
type Thing = Something | SomethingElse
Antes de esta definición,
Something
y
SomethingElse
no significaban nada.
Ahora ambos son del tipo
Thing
, que acabamos de definir.
type alias
se utiliza para dar un nombre a 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"
.
Aunque especificamos
thing
una
thing
es una
String
y
aliasedStringIdentity
toma una
AliasedString
, no obtendremos un error de que hay 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
En Elm, no puedo entender cuándo
type
es la palabra clave adecuada frente a
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?
La clave es la palabra
alias
.
En el curso de la programación, cuando desea agrupar cosas que pertenecen juntas, lo coloca en un registro, como en el caso de un punto
{ x = 5, y = 4 }
o un registro de estudiante.
{ name = "Billy Bob", grade = 10, classof = 1998 }
Ahora, si necesitara pasar estos registros, 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 pudieras 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 }
Entonces un alias es una abreviatura de otra cosa.
Aquí, es una abreviatura para un tipo de registro.
Puede considerarlo como un nombre para un tipo de registro que usará con frecuencia.
Es por eso que se llama un alias: es otro nombre para el tipo de registro desnudo que está representado por
{ x:Int, y:Int }
Mientras que el
type
resuelve un problema diferente.
Si viene de OOP, es el problema que resuelve con la herencia, la sobrecarga del operador, 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 sucede esto es cuando se pasan 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 contenido en él. Solo cuando la carta llega a su destino, le importa cuál es el mensaje.
Del mismo modo, podríamos definir un
type
como una unión de todos los diferentes tipos de mensajes que podrían ocurrir.
Digamos que estamos implementando un sistema de mensajes entre estudiantes universitarios y sus padres.
Así que solo hay dos mensajes que los universitarios pueden enviar: "Necesito dinero de cerveza" y "Necesito calzoncillos".
type MessageHome = NeedBeerMoney | NeedUnderpants
Entonces, cuando diseñamos el sistema de enrutamiento, los tipos para nuestras funciones pueden simplemente pasar por
MessageHome
, en lugar de preocuparse por 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 que descubras de qué se trata.
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 de donde se enruta el mensaje y, por lo tanto, se procesa. Y usamos tipos de unión para tener un tipo único con el que lidiar cuando pasamos el mensaje, pero luego podemos usar una declaración de caso para descifrar exactamente qué mensaje era, para que podamos tratarlo.
La principal diferencia, según lo veo, es si el verificador de tipo le gritará si usa el tipo "sinómico".
Cree el siguiente archivo, póngalo 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 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
Un
alias
es solo un nombre más corto para algún otro tipo,
class
similar en OOP.
Exp:
type alias Point =
{ x : Int
, y : Int
}
Un
type
(sin alias) le permitirá definir su propio tipo, para que pueda definir tipos como
Int
,
String
, ... para su aplicación.
Por ejemplo, en el caso común, se puede usar para la descripción de un estado de la aplicación:
type AppState =
Loading --loading state
|Loaded --load successful
|Error String --Loading error
Para que pueda manejarlo fácilmente en
view
elm:
-- VIEW
...
case appState of
Loading -> showSpinner
Loaded -> showSuccessData
Error error -> showError
...
Creo que sabes la diferencia entre
type
y
type alias
.
Pero por qué y cómo usar el
type
y el
type alias
es importante con
elm
aplicación
elm
, ustedes pueden consultar el
artículo de Josh Clayton
Permítanme complementar las respuestas anteriores centrándome en casos de uso y proporcionando un pequeño contexto en las funciones y módulos del constructor.
Usos del
type alias
-
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 }
La definición del alias de tipo implica automáticamente la siguiente función de constructor (pseudocó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)
-
Especificar campos obligatorios
A veces lo llaman "registros extensibles", que pueden ser engañosos. Esta sintaxis se puede utilizar para especificar que espera 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
Luego 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.
-
Renombrar cosas
Puede hacer esto, porque los nuevos nombres podrían proporcionar un significado adicional más adelante en su código, como en este ejemplotype 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
.
-
Reexponer 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, quizás 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 de múltiples módulos.
Task
in core y Http.Request en Http son ejemplos para el primero, mientras que el par Json.Encode.Value y Json.Decode.Value 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 más detalles, consulte los usos del
type
continuació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 el # 1 como
module Data exposing (Person)
que expondrá el nombre del tipo, así como la función del constructor.
Usos de
type
-
Definir un tipo de unión etiquetado
Este es el caso de uso más común, un buen ejemplo es el tipoMaybe
en el núcleo :type Maybe a = Just a | Nothing
Cuando define un tipo, también define sus funciones de constructor. En caso de Quizás, estos son (pseudocódigo):
Just : a -> Maybe a Nothing : Maybe a
Lo que significa que si declaras este valor:
mayHaveANumber : Maybe Int
Puedes crearlo ya sea
mayHaveANumber = Nothing
o
mayHaveANumber = Just 5
Las etiquetas
Just
yNothing
no solo sirven como funciones de constructor, también sirven como destructores o patrones en una expresión decase
. Lo que significa que usando estos patrones puedes ver dentro de unMaybe
: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.
-
Ocultar detalles de implementación
Como se señaló anteriormente, es una elección deliberada que las funciones de construcción deMaybe
sean visibles para otros módulos.Hay otros casos, sin embargo, cuando el autor decide ocultarlos. Un ejemplo de esto en el núcleo es
Dict
. Como consumidor del paquete, no debería poder ver los detalles de implementación del algoritmo de árbol Rojo / Negro detrás deDict
y meterse directamente con los nodos. 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 cual a veces cosas como esta aparecen en el código
type Person = Person { name : String, age : Int }
A diferencia de la definición de
type alias
en la parte superior de esta publicación, esta sintaxis crea un nuevo tipo de "unión" con una sola función de constructor, pero esa función de constructor puede ocultarse de otros módulos / paquetes.Si el tipo se expone así:
module Data exposing (Person)
Solo el código en el módulo de
Data
puede crear un valor de persona y solo ese código puede coincidir con el patrón.