would pronunciacion pronuncia inglés ingles español escrita cómo como haskell operators

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 el zipWith con las listas, por lo que puede leerlo como "zip functors with", similar a leer fmap 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 un Functor
  • (<*>) , que encadena una función de múltiples argumentos a través de un Applicative
  • (=<<) , que une una función que ingresa una Monad 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í que Maybe f <*> Maybe a se puede pronunciar como aplicar Maybe f sobre Maybe a .

  • Podría cambiar pure nombre de pure a of , como lo hacen muchas bibliotecas de JavaScript. En JS puedes crear un Maybe with Maybe.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 ".