functional programming - ¿Por qué el uso de Maybe/Option no es tan generalizado en Clojure?
functional-programming monads (7)
¿Por qué Clojure, a pesar de tal énfasis en el paradigma funcional, no usa la mónada Maybe
/ Option
para representar valores opcionales? El uso de Option
es bastante generalizado en Scala, un lenguaje de programación funcional que utilizo regularmente.
¡Es importante recordar que el concepto de Mónada no se trata de tipos! Los sistemas tipo te ayudan a hacer cumplir las reglas (pero incluso Haskell no puede hacer cumplir todas las reglas, ya que algunas de ellas (las leyes de Monad) no pueden ser expresadas completamente por un sistema de tipos.
Las mónadas son sobre composición, lo cual es algo muy importante que todos hacemos todos los días en todos los lenguajes de programación. En todos los casos, la Mónada rastrea un "contexto extra" sobre lo que está sucediendo ... piense en ello como una caja que se aferra al valor actual. Las funciones se pueden aplicar a este valor, y el contexto adicional puede evolucionar como una preocupación ortogonal.
El tipo Maybe se trata de encadenar largas secuencias de cálculos juntos sin tener que decir nada sobre el fracaso (que es el "contexto adicional"). Es un patrón que mueve el "manejo de errores" fuera del cómputo y hacia la Mónada. Puede enhebrar una secuencia de cálculos en un Quiz y, en cuanto falla, el resto se ignora y el resultado final es "nada". Si todos tienen éxito, entonces su resultado final es la mónada que contiene el valor del resultado.
Esto le permite escribir código que está mucho menos enredado.
Clojure es compatible con las Mónadas, como señaló @deterb.
Bueno, hay una mónada Maybe, pero usa nil como Nothing, capturando solo la abstracción de la computación (si input = nil return nil else calc whatever con entrada) para evitar errores de punteros nulos pero no tiene la seguridad del tiempo de compilación estático. También hay fnil que tienen una misión similar, parches nulos con valores predeterminados y a -?> . Creo que el modo clojure está más orientado a devolver valores predeterminados que generan errores o nulos.
Clojure no está tipado estáticamente, por lo que no necesita las declaraciones estrictas this / that / whatever que sean necesarias en haskell (y, supongo, Scala). Si desea devolver una cadena, devuelve una cadena; si devuelve nil en su lugar, está bien también.
"Funcional" no se corresponde exactamente con "estricto tipeo en tiempo de compilación". Son conceptos ortogonales, y Clojure elige el tipado dinámico. De hecho, durante bastante tiempo no pude imaginar cómo podría implementar muchas de las funciones de orden superior como el map
y aún así conservar el tipado estático. Ahora que tengo una pequeña (muy poca) experiencia con Haskell, puedo ver que es posible, y de hecho a menudo bastante elegante. Sospecho que si juegas con Clojure por un tiempo, tendrás la experiencia opuesta: te darás cuenta de que las declaraciones de tipo no son necesarias para darte el tipo de poder que estás acostumbrado a tener en un lenguaje funcional.
En Clojure, el juego de palabras nil ofrece la mayor parte de la funcionalidad que Scala & Haskell obtiene de Option & Maybe.
**Scala** **Clojure**
Some(1) map (2+_) (if-let [a 1] (+ 2 a))
Some(1) match { (if-let [a 1]
case Some(x) => 2+x (+ 2 a)
case None => 4 4)
}
La opción de Scala y Haskell''s Maybe son ambas instancias de Applicative. Esto significa que puede usar valores de estos tipos en las comprensiones. Por ejemplo, Scala admite:
for { a <- Some(1)
b <- Some(2)
} yield a + b
Clojure''s para macro proporciona comprensiones sobre seq. A diferencia de las comprensiones monádicas, esta implementación permite mezclar tipos de instancias.
Aunque Clojure''s no se puede usar para componer funciones sobre múltiples valores nulos posibles, su funcionalidad es trivial de implementar.
(defn appun [f & ms]
(when (every? some? ms)
(apply f ms)))
Y llamándolo:
(appun + 1 2 3) #_=> 6
(appun + 1 2 nil) #_=> nil
Hay some->
y some->>
disponibles desde Clojure 1.5
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(some-> input :c :f :h inc))
user> 8
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(some-> input :c :z :h inc))
user> nil
(let [input {:a 1 :b 2 :c {:d 4 :e 5 :f {:h 7}}}]
(-> input :c :z :h inc))
user> NullPointerException clojure.lang.Numbers.ops (Numbers.java:1013)
La función some->
proporciona la opción [T].
Maybe / Option es un tipo. No tiene nada que ver con la programación funcional. Sí, algunos idiomas (Scala, haskell, ocaml) además de ser funcionales también proporcionan un sistema de tipo muy poderoso. La gente incluso dice sobre Haskell que es una programación CON TIPOS.
Otros (clojure, lisp) no proporcionan mucho en términos de tipos a pesar de que son lenguajes funcionales totalmente capaces. Su énfasis es diferente, y el tipo Maybe / Option no encaja. Simplemente no le da mucho en lenguaje dinámico. Por ejemplo, muchas funciones de clojure que operan en secuencias (listas, vectores, mapas) aceptan perfectamente nulo (nulo) y lo tratan como una estructura vacía.
(count nil) te dará 0. Al igual que (count [])
Clojure no puede llamarse "programación con tipos" y, por lo tanto, el tipo Maybe no tiene mucho sentido.
Yendo con @amalloy y comentando que Clojure, como idioma, no necesita un valor de retorno opcional.
No he hecho mucho con Scala, pero Clojure no necesita saber los detalles estrictos sobre el tipo de devolución para poder trabajar con un valor. Es casi como si una mónada de Maybe estuviera incrustada y formara parte de la evaluación Clojure normal, ya que muchas de las operaciones, si se realizan en nil
, devuelven nil
.
Eché un vistazo rápido a la biblioteca de Clojure-Contrib, y tienen un paquete de mónada que tal vez quiera mirar. Otro elemento que realmente me dio la pista de cómo uno usaría las Mónadas en Clojure, es el tutorial de Cosmin sobre Mónadas en Clojure . Fue esto lo que me ayudó a conectar cómo la funcionalidad que se establece más explícitamente en Scala se maneja como parte del lenguaje dinámico de Clojure.