pronunciacion - Haskell: ¿Cómo se pronuncia<*>?
would pronunciacion escrita (5)
Lo siento, no sé realmente mi matemática, así que tengo curiosidad por cómo pronunciar las funciones en la clase de tipo Applicative
Saber tus matemáticas, o no, es en gran medida irrelevante aquí, creo. Como seguramente sabrá, Haskell toma prestados algunos términos de varios campos de matemática abstracta, más notablemente la Teoría de Categoría , de donde obtenemos funtores y mónadas. El uso de estos términos en Haskell difiere un poco de las definiciones matemáticas formales, pero de todos modos son lo suficientemente cercanos como para ser buenos términos descriptivos.
La clase de tipo Applicative
ubica en algún lugar entre Functor
y Monad
, por lo que uno esperaría que tenga una base matemática similar. La documentación del módulo Control.Applicative
comienza con:
Este módulo describe una estructura intermedia entre un functor y una mónada: proporciona expresiones puras y secuencia, pero no vinculante. (Técnicamente, un fuerte funcionador monoidal laxo.)
Hmm.
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
No es tan pegadizo como Monad
, creo.
A lo que todo esto se reduce básicamente es a que Applicative
no corresponde a ningún concepto que sea particularmente interesante matemáticamente, por lo que no hay términos preestablecidos por ahí que capturen la forma en que se usa en Haskell. Por lo tanto, deje las matemáticas de lado por ahora.
Si queremos saber a qué llamar (<*>)
podría ayudar saber qué significa básicamente.
Entonces, ¿qué pasa con Applicative
, de todos modos, y por qué lo llamamos así?
Lo que Applicative
en la práctica es una forma de levantar funciones arbitrarias en un Functor
. Considere la combinación de Maybe
(posiblemente el Functor
no trivial más simple) y Bool
(también el tipo de datos no triviales más simple).
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
La función fmap
nos permite levantar y not
trabajar en Bool
para trabajar en Maybe Bool
. Pero, ¿y si queremos levantar (&&)
?
maybeAnd'' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd'' = fmap (&&)
Bueno, eso no es lo que queremos en absoluto ! De hecho, es bastante inútil. Podemos intentar ser astutos y colar a otro Bool
en Maybe
por la espalda ...
maybeAnd'''' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'''' x y = fmap ($ y) (fmap (&&) x)
... pero eso no está bien. Por un lado, está mal. Por otro lado, es feo . Podríamos seguir intentándolo, pero resulta que no hay forma de levantar una función de múltiples argumentos para trabajar en un Functor
arbitrario . ¡Molesto!
Por otro lado, podríamos hacerlo fácilmente si Monad
instancia de Monad
Maybe
:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x'' <- x
y'' <- y
return (x'' && y'')
Ahora, eso es una gran molestia solo para traducir una función simple, por lo que Control.Monad
proporciona una función para hacerlo automáticamente, liftM2
. El 2 en su nombre se refiere al hecho de que funciona en funciones de exactamente dos argumentos; funciones similares existen para 3, 4 y 5 funciones de argumento. Estas funciones son mejores , pero no perfectas, y especificar el número de argumentos es feo y torpe.
Lo que nos lleva al documento que introdujo la clase de tipo Aplicativo . En ella, los autores hacen esencialmente dos observaciones:
- Levantar funciones de múltiples argumentos en un
Functor
es algo muy natural de hacer - Hacerlo no requiere las capacidades completas de una
Monad
La aplicación de función normal se escribe por simple yuxtaposición de términos, por lo que para hacer que la "aplicación elevada" sea lo más simple y natural posible, el documento presenta operadores de infijo que se presentan para su aplicación, levantados al Functor
y una clase de tipo para proporcionar lo necesario para ese.
Todo lo cual nos lleva al siguiente punto: (<*>)
simplemente representa la aplicación de función, entonces, ¿por qué pronunciarla de manera diferente a como lo hace el "operador de yuxtaposición" de espacio en blanco?
Pero si eso no es muy satisfactorio, podemos observar que el módulo Control.Monad
también proporciona una función que hace lo mismo para las mónadas:
ap :: (Monad m) => m (a -> b) -> m a -> m b
Donde ap
es, por supuesto, la abreviatura de "aplicar". Como cualquier Monad
puede ser Applicative
, y ap
solo necesita el subconjunto de características presentes en esta última, podemos quizás decir que si (<*>)
no fuera un operador, debería llamarse ap
.
También podemos abordar las cosas desde la otra dirección. La operación de elevación del Functor
se llama fmap
porque es una generalización de la operación del map
en las listas. ¿En qué tipo de función funcionarían las listas (<*>)
? Hay lo que ap
hace en las listas, por supuesto, pero eso no es particularmente útil por sí mismo.
De hecho, hay una interpretación quizás más natural para las listas. ¿Qué te viene a la mente cuando miras la siguiente firma de tipo?
listApply :: [a -> b] -> [a] -> [b]
Hay algo tan tentador en la idea de alinear las listas en paralelo, aplicando cada función en la primera al elemento correspondiente de la segunda. Desafortunadamente para nuestro viejo amigo Monad
, esta simple operación viola las leyes de mónadas si las listas son de diferentes longitudes. Pero hace un buen Applicative
, en cuyo caso (<*>)
convierte en una forma de encadenar una versión generalizada de zipWith
, así que tal vez podemos imaginar llamarlo fzipWith
?
Esta idea de compresión realmente nos da un círculo completo. ¿Recuerda eso de las matemáticas antes, sobre los funtores monoidales? Como su nombre indica, se trata de una forma de combinar la estructura de monoides y funtores, que son clases familiares de tipo Haskell:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
¿Cómo se verían si los pusieran juntos en una caja y lo sacudieran un poco? Desde Functor
mantendremos la idea de una estructura independiente de su parámetro de tipo , y desde Monoid
mantendremos la forma general de las funciones:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ?
mfAppend :: f ? -> f ? -> f ?
No queremos suponer que hay una manera de crear un Functor
verdaderamente "vacío", y no podemos invocar un valor de un tipo arbitrario, por lo que corregiremos el tipo de mfEmpty
como f ()
.
Tampoco queremos obligar a mfAppend
a necesitar un parámetro de tipo consistente, por lo que ahora tenemos esto:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f ?
¿Cuál es el tipo de resultado para mfAppend
? Tenemos dos tipos arbitrarios de los que no sabemos nada, por lo que no tenemos muchas opciones. Lo más sensato es mantener ambos:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
En este momento mfAppend
es ahora claramente una versión generalizada de zip
en las listas, y podemos reconstruir fácilmente Applicative
:
mfPure x = fmap (/() -> x) mfEmpty
mfApply f x = fmap (/(f, x) -> f x) (mfAppend f x)
Esto también nos muestra que pure
está relacionado con el elemento de identidad de un Monoid
, por lo que otros buenos nombres para él podrían ser cualquier cosa que sugiera un valor unitario, una operación nula, o tal.
Eso fue largo, así que para resumir:
-
(<*>)
es solo una aplicación de función modificada, por lo que puede leerla como "ap" o "aplicar", o eliminarla por completo como lo haría con la aplicación de función normal. -
(<*>)
también generaliza elzipWith
con las listas, por lo que puede leerlo como "zip functors with", similar a leerfmap
como "asignar un functor con".
El primero está más cerca de la intención de la clase de tipo Applicative
, como su nombre indica, así que eso es lo que recomiendo.
De hecho, animo el uso liberal, y la no pronunciación, de todos los operadores de aplicaciones levantadas :
-
(<$>)
, que levanta una función de argumento único en unFunctor
-
(<*>)
, que encadena una función de múltiples argumentos a través de unApplicative
-
(=<<)
, que une una función que ingresa unaMonad
en un cálculo existente
Los tres son, en el fondo, una aplicación de función regular, un poco condimentada.
¿Cómo pronuncias estas funciones en la clase de tipo Applicative?
(<*>) :: f (a -> b) -> f a -> f b
(*>) :: f a -> f b -> f b
(<*) :: f a -> f b -> f a
(Es decir, si no fueran operadores, ¿cómo se los llamaría?)
Como nota al margen, si pudieras renombrar pure
a algo más amigable para los no matemáticos, ¿cómo lo llamarías?
Como no tengo ninguna ambición de mejorar share , abordaré la más esponjosa:
Si pudieras renombrar
pure
a algo más amigable para los podunks como yo, ¿cómo lo llamarías?
Como alternativa, especialmente dado que no hay fin para la constante angustia y traición llena de lágrimas contra la versión de la Monad
, llamada " return
", propongo otro nombre, que sugiere su función de una manera que puede satisfacer el más imperativo de programadores imperativos, y el más funcional de ... bueno, con suerte, todos pueden quejarse de lo mismo: inject
.
Toma un valor. "Inyectar" en el Functor
, Applicative
, Monad
o lo que-tengas. Voto por " inject
" y aprobé este mensaje.
En breve:
<*>
puedes llamarlo aplicar . Así queMaybe f <*> Maybe a
se puede pronunciar como aplicarMaybe f
sobreMaybe a
.Podría cambiar
pure
nombre depure
aof
, como lo hacen muchas bibliotecas de JavaScript. En JS puedes crear unMaybe
withMaybe.of(a)
.
Además, el wiki de Haskell tiene una página sobre la pronunciación de los operadores del lenguaje here
Siempre me gustó wrap
. Tome un valor y envuélvalo en un Functor, Aplicativo, Mónada. También funciona bien cuando se usa en una oración con instancias concretas: []
, Maybe
, etc. "Toma un valor y lo envuelve en una X
".
(<*>) -- Tie Fighter
(*>) -- Right Tie
(<*) -- Left Tie
pure -- also called "return"
Fuente: Haskell Programming from First Principles , de Chris Allen y Julie Moronuki