En realidad, es solo un constructor de datos normal que se define en el Preludio , que es la biblioteca estándar que se importa automáticamente en cada módulo.
Lo que quizás es, estructuralmente
La definición se parece a esto:
data Maybe a = Just a
| Nothing
Esa declaración define un tipo, Maybe a
que está parametrizado por una variable de tipo a
, lo que solo significa que puede usarlo con cualquier tipo en lugar de a
.
Construyendo y Destruyendo
El tipo tiene dos constructores Just a
y Nothing
. Cuando un tipo tiene varios constructores, significa que un valor del tipo debe haberse construido con solo uno de los posibles constructores. Para este tipo, se construyó un valor mediante Just
o Nothing
, no hay otras posibilidades (sin error).
Dado que Nothing
no tiene un tipo de parámetro, cuando se usa como constructor, nombra un valor constante que es un miembro de tipo Maybe a
para todos los tipos a
. Pero el Just
constructor tiene un parámetro de tipo, lo que significa que cuando se usa como constructor actúa como una función de tipo a
a Maybe a
, es decir, tiene el tipoa -> Maybe a
Entonces, los constructores de un tipo construyen un valor de ese tipo; el otro lado de las cosas es cuando le gustaría usar ese valor, y ahí es donde entra en juego la coincidencia de patrones. A diferencia de las funciones, los constructores se pueden usar en expresiones de enlace de patrones, y esta es la forma en que puede realizar análisis de casos de valores que pertenecen a tipos con más de un constructor.
Para usar un Maybe a
valor en una coincidencia de patrones, debe proporcionar un patrón para cada constructor, así:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
En esa expresión de caso, el primer patrón coincidiría si el valor fuera Nothing
, y el segundo coincidiría si el valor se construyera con Just
. Si el segundo coincide, también vincula el nombre val
al parámetro que se pasó al Just
constructor cuando se construyó el valor con el que está haciendo coincidir.
Lo que quizás significa
Quizás ya estaba familiarizado con cómo funcionaba esto; Realmente no hay magia en los Maybe
valores, es solo un tipo de datos algebraicos Haskell (ADT) normal. Pero se usa bastante porque efectivamente "eleva" o extiende un tipo, como el Integer
de su ejemplo, a un nuevo contexto en el que tiene un valor adicional ( Nothing
) que representa una falta de valor. El sistema de tipos requiere que verifique ese valor adicional antes de que le permita obtener el Integer
que podría estar allí. Esto evita una gran cantidad de errores.
Hoy en día, muchos lenguajes manejan este tipo de valor "sin valor" a través de referencias NULL. Tony Hoare, un científico informático eminente (inventó Quicksort y es un ganador del premio Turing), reconoce esto como su "error de mil millones de dólares" . El tipo Maybe no es la única forma de solucionar este problema, pero ha demostrado ser una forma eficaz de hacerlo.
Quizás como Functor
La idea de transformar un tipo en otro de modo que las operaciones en el tipo antiguo también se puedan transformar para trabajar en el nuevo tipo es el concepto detrás de la clase de tipos Haskell llamada Functor
, que Maybe a
tiene una instancia útil de.
Functor
proporciona un método llamado fmap
, que asigna funciones que varían sobre valores desde el tipo base (como Integer
) a funciones que varían sobre valores desde el tipo elevado (como Maybe Integer
). Una función transformada con fmap
para trabajar en un Maybe
valor funciona así:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
Entonces, si tiene un Maybe Integer
valor m_x
y una Int -> Int
función f
, puede fmap f m_x
aplicar la función f
directamente al Maybe Integer
sin preocuparse si realmente tiene un valor o no. De hecho, podría aplicar una cadena completa de Integer -> Integer
funciones elevadas a los Maybe Integer
valores y solo tendrá que preocuparse por verificar explícitamente Nothing
una vez cuando haya terminado.
Quizás como una mónada
No estoy seguro de qué tan familiarizado está con el concepto de un Monad
todavía, pero al menos lo ha usado IO a
antes, y la firma de tipo se IO a
ve muy similar a Maybe a
. Aunque IO
es especial en el sentido de que no le expone sus constructores y, por lo tanto, solo puede ser "ejecutado" por el sistema de tiempo de ejecución de Haskell, también es un Functor
complemento de Monad
. De hecho, hay un sentido importante en el que a Monad
es solo un tipo especial Functor
con algunas características adicionales, pero este no es el lugar para entrar en eso.
De todos modos, a las mónadas les gustan IO
los tipos de mapas de nuevos tipos que representan "cálculos que dan como resultado valores" y puedes convertir funciones en Monad
tipos a través de una fmap
función muy similar a la llamada liftM
que convierte una función regular en un "cálculo que da como resultado el valor obtenido al evaluar el función."
Probablemente haya adivinado (si ha leído hasta aquí) que Maybe
también es un Monad
. Representa "cálculos que podrían no devolver un valor". Al igual que con el fmap
ejemplo, esto le permite hacer un montón de cálculos sin tener que buscar errores explícitamente después de cada paso. Y de hecho, la forma en que Monad
se construye la instancia, un cálculo de Maybe
valores se detiene tan pronto como Nothing
se encuentra a, por lo que es como un aborto inmediato o una devolución sin valor en medio de un cálculo.
Podrías haber escrito quizás
Como dije antes, no hay nada inherente al Maybe
tipo que está integrado en la sintaxis del lenguaje o en el sistema de ejecución. Si Haskell no lo proporcionó de forma predeterminada, ¡usted mismo podría proporcionar todas sus funciones! De hecho, podría volver a escribirlo usted mismo de todos modos, con diferentes nombres, y obtener la misma funcionalidad.
Espero que entiendas el Maybe
tipo y sus constructores ahora, pero si todavía hay algo que no está claro, ¡avísame!
Maybe
donde otros lenguajes usaríannull
onil
(con desagradablesNullPointerException
s acechando en cada esquina). Ahora, otros lenguajes también comienzan a usar esta construcción: Scala asOption
, e incluso Java 8 tendrá elOptional
tipo.La mayoría de las respuestas actuales son explicaciones altamente técnicas de cómo
Just
funcionan los amigos; Pensé que podría intentar explicar para qué sirve.Muchos lenguajes tienen un valor como
null
ese que se puede usar en lugar de un valor real, al menos para algunos tipos. Esto ha enfurecido a mucha gente y ha sido considerado como una mala decisión. Aún así, a veces es útil tener un valor comonull
para indicar la ausencia de algo.Haskell resuelve este problema haciéndote marcar explícitamente lugares donde puedes tener un
Nothing
(su versión de anull
). Básicamente, si su función normalmente devolvería el tipoFoo
, en su lugar debería devolver el tipoMaybe Foo
. Si desea indicar que no hay valor, regreseNothing
. Si desea devolver un valorbar
, debe volverJust bar
.Básicamente, si no puede tener
Nothing
, no lo necesitaJust
. Si puedeNothing
, lo necesitaJust
.No tiene nada de mágico
Maybe
; está construido sobre el sistema de tipos Haskell. Eso significa que puede usar todos los trucos habituales de coincidencia de patrones de Haskell con él.fuente
Dado un tipo
t
, un valor deJust t
es un valor de tipo existentet
, dondeNothing
representa una falla para alcanzar un valor, o un caso en el que tener un valor no tendría sentido.En su ejemplo, tener un saldo negativo no tiene sentido, por lo que, si tal cosa ocurriera, se reemplaza por
Nothing
.Para otro ejemplo, esto podría usarse en la división, definiendo una función de división que toma
a
yb
, y devuelveJust a/b
sib
es distinto de cero, y deNothing
otro modo. A menudo se usa así, como una alternativa conveniente a las excepciones, o como su ejemplo anterior, para reemplazar valores que no tienen sentido.fuente
Just
, su código no se marcaría. El motivoJust
es mantener los tipos adecuados. Hay un tipo (una mónada, en realidad, pero es más fácil pensar que es solo un tipo)Maybe t
, que consta de elementos de la formaJust t
yNothing
. Dado queNothing
tiene tipoMaybe t
, una expresión que puede evaluarse como unoNothing
o algún valor de tipot
no se escribe correctamente. Si una función regresaNothing
en algunos casos, cualquier expresión que use esa función debe tener alguna forma de verificar eso (isJust
o una declaración de caso), para manejar todos los casos posibles.Maybe t
es solo un tipo. El hecho de que haya unaMonad
instancia deMaybe
no la transforma en algo que no sea un tipo.Una función total a-> b puede encontrar un valor de tipo b para cada valor posible de tipo a.
En Haskell no todas las funciones son totales. En este caso particular, la función
lend
no es total; no está definida para el caso en el que el saldo es menor que la reserva (aunque, a mi gusto, tendría más sentido no permitir que newBalance sea menor que la reserva; tal como está, puede pedir prestado 101 de un saldo de 100).Otros diseños que se ocupan de funciones no totales:
lend
podría escribir para devolver el saldo anterior, si no se cumple la condición para prestarEstas son limitaciones de diseño necesarias en lenguajes que no pueden imponer la totalidad de funciones (por ejemplo, Agda puede, pero eso conduce a otras complicaciones, como volverse turing-incompleto).
El problema de devolver un valor especial o lanzar excepciones es que es fácil para la persona que llama omitir el manejo de tal posibilidad por error.
El problema de descartar silenciosamente una falla también es obvio: está limitando lo que la persona que llama puede hacer con la función. Por ejemplo, si se
lend
devuelve el saldo anterior, la persona que llama no tiene forma de saber si el saldo ha cambiado. Puede que sea un problema o no, según el propósito previsto.La solución de Haskell obliga a quien llama a una función parcial a lidiar con el tipo like
Maybe a
, oEither error a
debido al tipo de retorno de la función.De esta manera,
lend
tal como se define, es una función que no siempre calcula el nuevo saldo; en algunas circunstancias, no se define el nuevo saldo. Señalamos esta circunstancia a la persona que llama devolviendo el valor especial Nothing o ajustando el nuevo saldo en Just. La persona que llama ahora tiene la libertad de elegir: manejar la falta de préstamo de una manera especial o ignorar y usar el saldo anterior, por ejemplomaybe oldBalance id $ lend amount oldBalance
,.fuente
La función
if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a)
debe tener el mismo tipo deifTrue
yifFalse
.Entonces, cuando escribimos
then Nothing
, debemos usarMaybe a
type inelse f
fuente
Nothing
yJust newBalance
explícitamente.Just
.