haskell - traducir - traductor español frances
Traducir de la mónada al aplicativo. (3)
Su ejemplo puede reescribirse progresivamente a una forma que se asemeja más claramente a un Aplicativo:
do
many1 space
ds <- many1 digit
return $ read ds
definición de notación
do
:many1 space >> (many1 digit >>= /ds -> return $ read ds)
definición de
$
:many1 space >> (many1 digit >>= /ds -> return (read ds))
definición de
.
:many1 space >> (many1 digit >>= (return . read))
3ª ley de la mónada (asociatividad):
(many1 space >> many1 digit) >>= (return . read)
definición de
liftM
(en notaciónliftM
-do
):liftM read (many1 space >> many1 digit)
Esto es (o debería ser, si no me he equivocado :)) de comportamiento idéntico a tu ejemplo.
Ahora, si reemplaza liftM
con fmap
con <$>
, y >>
con *>
, obtendrá el Aplicativo:
read <$> (many1 space *> many1 digit)
Esto es válido porque liftM
, fmap
y <$>
generalmente se supone que son sinónimos, como son >>
y *>
.
Todo esto funciona y podemos hacerlo porque el ejemplo original no usó el resultado de ningún analizador para crear un analizador siguiente.
OK, así que sé lo que contiene la clase de tipo Applicative
y por qué es útil. Pero no puedo delimitar completamente mi cerebro sobre cómo lo usarías en un ejemplo no trivial.
Considere, por ejemplo, el siguiente analizador Parsec bastante simple:
integer :: Parser Integer
integer = do
many1 space
ds <- many1 digit
return $ read ds
Ahora, ¿cómo diablos escribirías eso sin usar la instancia de Parser
para Parser
? Mucha gente afirma que esto se puede hacer y es una buena idea, pero no puedo entender cómo exactamente.
Yo escribo
integer :: Parser Integer
integer = read <$ many1 space <*> many1 digit
Hay un grupo de operadores de creación de analizadores asociativos (como aplicaciones) a la izquierda <$>
, <*>
, <$
, <*
. La cosa en el extremo izquierdo debe ser la función pura que ensambla el valor del resultado de los valores del componente. La cosa a la derecha de cada operador debe ser un analizador, dando colectivamente los componentes de la gramática de izquierda a derecha. El operador a utilizar depende de dos opciones, de la siguiente manera.
the thing to the right is signal / noise
_________________________
the thing to the left is /
+-------------------
pure / | <$> <$
a parser | <*> <*
Entonces, habiendo elegido read :: String -> Integer
como la función pura que entregará la semántica del analizador, podemos clasificar el espacio inicial como "ruido" y el grupo de dígitos como "señal", por lo tanto
read <$ many1 space <*> many1 digit
(..) (.........) (.........)
pure noise parser |
(.................) |
parser signal parser
(.................................)
parser
Puedes combinar múltiples posibilidades con
p1 <|> ... <|> pn
y expresa imposibilidad con
empty
Rara vez es necesario nombrar componentes en analizadores, y el código resultante se parece más a una gramática con semántica agregada.
integer :: Parser Integer
integer = read <$> (many1 space *> many1 digit)
O
integer = const read <$> many1 space <*> many1 digit
Si crees que cualquiera de estos son más legibles depende de ti.