¿cómo se representan los tipos de suma, también conocidos como uniones con etiquetas y registros de variantes? Algo así como Either a b
en Haskell o Either[+A, +B]
en Scala.
Either
tiene dos usos: para devolver un valor de uno de dos tipos o a retorno dos valores del mismo tipo que deben tener diferentes semántica sobre la base de la etiqueta.
El primer uso solo es importante cuando se usa un sistema de tipo estático. Either
es básicamente la solución mínima posible dadas las restricciones del sistema de tipo Haskell. Con un sistema de tipo dinámico, puede devolver valores de cualquier tipo que desee. Either
no es necesario.
El segundo uso es significativo, pero se puede lograr simplemente en dos (o más) formas:
{:tag :left :value 123} {:tag :right :value "hello"}
{:left 123} {:right "hello"}
Lo que me gustaría para asegurarse, es que: la etiqueta siempre está ahí, y puede tomar solo uno de los valores especificados , y el valor correspondiente es consistentemente del mismo tipo/comportamiento y no puede ser nulo, y allí es una manera fácil de ver que yo cuidé todos los casos en el código.
Si desea asegurarse de esto estáticamente, Clojure probablemente no sea su idioma. El motivo es simple: las expresiones no tienen los tipos hasta el tiempo de ejecución, hasta que devuelven un valor.
El motivo por el cual una macro no funcionará es que al momento de la expansión de macros, no tiene valores de tiempo de ejecución, y por lo tanto, tipos de tiempo de ejecución. Tiene construcciones en tiempo de compilación como símbolos, átomos, sexpresiones, etc. Puede puede eval
se considera, pero el uso de eval
se considera una mala práctica por un número de razones .
Sin embargo, podemos hacer un muy buen trabajo en tiempo de ejecución.
- Lo que me gustaría para asegurar, es que: la etiqueta siempre está ahí,
- y puede tomar sólo uno de los valores especificados
- y correspondiente valor es consistente del mismo tipo/comportamiento
- y no puede ser nulo
- y hay una manera fácil de ver que me ocupé de todos los casos en el código.
Mi estrategia será convertir todo lo que normalmente es estático (en Haskell) en tiempo de ejecución. Vamos a escribir algunos códigos.
;; let us define a union "type" (static type to runtime value)
(def either-string-number {:left java.lang.String :right java.lang.Number})
;; a constructor for a given type
(defn mk-value-of-union [union-type tag value]
(assert (union-type tag)) ; tag is valid
(assert (instance? (union-type tag) value)) ; value is of correct type
(assert value)
{:tag tag :value value :union-type union-type})
;; "conditional" to ensure that all the cases are handled
;; take a value and a map of tags to functions of one argument
;; if calls the function mapped to the appropriate tag
(defn union-case-fn [union-value tag-fn]
;; assert that we handle all cases
(assert (= (set (keys tag-fn))
(set (keys (:union-type union-value)))))
((tag-fn (:tag union-value)) (:value union-value)))
;; extra points for wrapping this in a macro
;; example
(def j (mk-value-of-union either-string-number :right 2))
(union-case-fn j {:left #(println "left: " %) :right #(println "right: " %)})
=> right: 2
(union-case-fn j {:left #(println "left: " %)})
=> AssertionError Assert failed: (= (set (keys tag-fn)) (set (keys (:union-type union-value))))
Este código utiliza la siguiente Clojure idiomática construye:
- programación basada en datos: crear una estructura de datos que representa el "tipo". Este valor es inmutable y de primera clase, y tiene todo el lenguaje disponible para implementar la lógica con él. Esto es algo que no creo que Haskell pueda hacer: manipular tipos en tiempo de ejecución.
- Uso de mapas para representar valores.
- Programación de orden superior: pasando un mapa de fns a otra función.
De manera opcional, puede usar protocolos si está usando Either
para polimorfismo. De lo contrario, si está interesado en la etiqueta, algo del formulario {:tag :left :value 123}
es el más idiomático.Es frecuente encontrar que algo como esto:
;; let's say we have a function that may generate an error or succeed
(defn somefunction []
...
(if (some error condition)
{:status :error :message "Really bad error occurred."}
{:status :success :result [1 2 3]}))
;; then you can check the status
(let [r (somefunction)]
(case (:status r)
:error
(println "Error: " (:message r))
:success
(do-something-else (:result r))
;; default
(println "Don't know what to do!")))
una gran respuesta al LispCast: http://www.lispcast.com/idiomatic-way-to-represent-either – sastanin