Haskell: ¿Cómo se pronuncia <*>? [cerrado]

109

¿Cómo se pronuncian estas funciones en la clase de tipos 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 llamarían?)

Como nota al margen, si pudieras cambiar el nombre purea algo más amigable para los no matemáticos, ¿cómo lo llamarías?

J Cooper
fuente
6
@J Cooper ... ¿podrías escuchar cómo lo pronunciamos? :) Es posible que desee publicar una solicitud en meta.stackoverflow.com para una función de grabación y reproducción de voz :).
Kiril
8
Se pronuncia "Dios mío, realmente se estaban quedando sin operadores, ¿no?" Además, un buen nombre para purepodría ser makeApplicative.
Chuck
@Lirik, bueno, supongo que por pronunciar me refiero a "cómo llaman a esto" :) @Chuck, publica tu puresugerencia como respuesta y te votaré
J Cooper
6
(<*>) es la versión Control.Applicative de "ap" de Control.Monad, por lo que "ap" es probablemente el nombre más apropiado.
Edward KMETT
11
Yo lo llamaría cíclope, pero solo soy yo.
RCIX

Respuestas:

245

Lo siento, realmente no sé mis matemáticas, así que tengo curiosidad por saber cómo pronunciar las funciones en la clase de tipos Aplicativos

Saber sus matemáticas, o no, es en gran parte irrelevante aquí, creo. Como probablemente sepa, Haskell toma prestados algunos fragmentos de terminología de varios campos de las matemáticas abstractas, sobre todo la teoría de categorías , de donde obtenemos functores y mónadas. El uso de estos términos en Haskell difiere un poco de las definiciones matemáticas formales, pero por lo general están lo suficientemente cerca como para ser buenos términos descriptivos de todos modos.

La Applicativeclase de tipo se encuentra en algún lugar entre Functory Monad, por lo que uno esperaría que tuviera una base matemática similar. La documentación del Control.Applicativemódulo comienza con:

Este módulo describe una estructura intermedia entre un funtor y una mónada: proporciona expresiones puras y secuenciación, pero sin vinculación. (Técnicamente, un functor monoidal laxo fuerte).

Hmm.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

No es tan pegadizo como Monad, creo.

Básicamente, todo esto se reduce a que Applicativeno corresponde a ningún concepto que sea particularmente interesante matemáticamente, por lo que no hay términos prefabricados que capturen la forma en que se usa en Haskell. Por lo tanto, deje las matemáticas a un lado por ahora.


Si queremos saber cómo llamarlo (<*>), puede ser útil saber qué significa básicamente.

Entonces, ¿qué pasa Applicative, de todos modos, y por qué lo llamamos así?

En la Applicativepráctica, lo que equivale a una forma de convertir funciones arbitrarias en a Functor. Considere la combinación de Maybe(posiblemente el tipo de datos no trivial más simple Functor) y Bool(del mismo modo, el tipo de datos no trivial más simple).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

La función fmapnos permite pasar notde trabajar Boola trabajar Maybe Bool. ¿Pero y si queremos levantarnos (&&)?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Bueno, ¡eso no es lo que queremos en absoluto ! De hecho, es bastante inútil. Podemos tratar de ser inteligentes y colarse otra Boolen Maybepor la parte posterior ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... pero eso no es bueno. Por un lado, está mal. Por otra parte, 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 arbitrarioFunctor . ¡Molesto!

Por otro lado, podríamos hacerlo fácilmente si usamos Maybela Monadinstancia de:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Ahora, traducir una función simple es muy complicado, por lo que Control.Monadproporciona una función para hacerlo automáticamente liftM2. El 2 en su nombre se refiere al hecho de que trabaja en funciones de exactamente dos argumentos; existen funciones similares para funciones de 3, 4 y 5 argumentos. Estas funciones son mejores , pero no perfectas, y especificar el número de argumentos es feo y torpe.

Lo que nos lleva al artículo que introdujo la clase de tipo Aplicativo . En él, los autores hacen esencialmente dos observaciones:

  • Elevar funciones de múltiples argumentos a un Functores algo muy natural de hacer
  • Hacerlo no requiere todas las capacidades de un Monad

La aplicación de función normal se escribe mediante una 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 artículo presenta operadores infijos para sustituir la aplicación, elevados enFunctor y una clase de tipo para proporcionar lo que se necesita para eso. .

Todo lo cual nos lleva al siguiente punto: (<*>)simplemente representa la aplicación de la función, entonces, ¿por qué pronunciarla de manera diferente a como lo hace el "operador de yuxtaposición" de espacios en blanco?

Pero si eso no es muy satisfactorio, podemos observar que el Control.Monadmódulo 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 apes, por supuesto, la abreviatura de "aplicar". Dado que cualquiera Monadpuede ser Applicative, y apnecesita solo el subconjunto de características presentes en este último, quizás podamos decir que si (<*>)no fuera un operador, debería llamarse ap.


También podemos abordar las cosas desde la otra dirección. La Functoroperación de elevación se llama fmapporque es una generalización de la mapoperación en listas. ¿Qué tipo de función en las listas funcionaría (<*>)? Hay lo que aphace en las listas, por supuesto, pero eso no es particularmente útil por sí solo.

De hecho, existe una interpretación quizás más natural para las listas. ¿Qué le viene a la mente cuando mira 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 de la primera al elemento correspondiente de la segunda. Desafortunadamente para nuestro viejo amigo Monad, esta simple operación viola las leyes de las mónadas si las listas tienen diferentes longitudes. Pero funciona bien Applicative, en cuyo caso se (<*>)convierte en una forma de encadenar una versión generalizada de zipWith, así que quizás podamos imaginarnos llamarlo fzipWith.


Esta idea de cremallera en realidad nos trae un círculo completo. ¿Recuerda lo anterior de las matemáticas, sobre los functores monoidales? Como su nombre indica, estas son una forma de combinar la estructura de monoides y functores, los cuales son clases de tipos familiares de 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 estos si los pones en una caja juntos y la agitas un poco? De Functormantendremos la idea de una estructura independiente de su parámetro de tipo , y de Monoidmantendremos la forma general de las funciones:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

No queremos asumir que hay una manera de crear un verdadero "vacío" Functor, y no podemos conjurar un valor de un tipo arbitrario, así que arreglaremos el tipo de mfEmptycomo f ().

Tampoco queremos forzar la mfAppendnecesidad de un parámetro de tipo consistente, así que ahora tenemos esto:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

¿Para qué es el tipo de resultado 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 ese punto mfAppendahora es claramente una versión generalizada de zipen listas, y podemos reconstruir Applicativefácilmente:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Esto también nos muestra que pureestá relacionado con el elemento de identidad de a Monoid, por lo que otros buenos nombres para él podrían ser cualquier cosa que sugiera un valor unitario, una operación nula o algo así.


Eso fue largo, para resumir:

  • (<*>) es sólo una aplicación de función modificada, por lo que puede leerla como "ap" o "aplicar", o elírsela por completo como lo haría con una aplicación de función normal.
  • (<*>)también generaliza de forma aproximada zipWithen listas, por lo que puede leerlo como "zip fmapfunctor con" , de manera similar a leer como "mapear un functor con".

El primero está más cerca de la intención de la Applicativeclase de tipo, como sugiere el nombre, así que eso es lo que recomiendo.

De hecho, recomiendo el uso liberal y la no pronunciación de todos los operadores de aplicaciones levantados :

  • (<$>), que eleva una función de un solo argumento a una Functor
  • (<*>), que encadena una función de múltiples argumentos a través de una Applicative
  • (=<<), que une una función que ingresa a Monaden un cálculo existente

Los tres son, en el fondo, solo una aplicación de función normal, un poco condimentada.

CA McCann
fuente
6
@Colin Cochrane: ¿Estás seguro de que no escribiste mal "prolijo" allí? :) Pero bueno, ¡me lo llevo! Siempre siento que Applicativey el estilo idiomático funcional que promueve no reciben suficiente amor, así que no pude resistir la oportunidad de ensalzar un poco sus virtudes como un medio para explicar cómo (no) pronuncio (<*>).
CA McCann
+1! Siéntase libre de ser igualmente prolijo en stackoverflow.com/questions/2104446/…
Greg Bacon
6
¡Ojalá Haskell tuviera azúcar de sintaxis para Applicative's! Algo parecido [| f a b c d |](como sugiere el artículo original). Entonces no necesitaríamos el <*>combinador y se referiría a dicha expresión como un ejemplo de "aplicación de función en un contexto functorial"
Tom Crockett
1
@FredOverflow: No, me refiero Monad. O Functoro Monoidcualquier otra cosa que tenga un término bien establecido que involucre menos de tres adjetivos. "Aplicativo" es simplemente un nombre poco inspirador, aunque razonablemente descriptivo, colocado en algo que más bien lo necesitaba.
CA McCann
1
@pelotom: vea [ stackoverflow.com/questions/12014524/… donde gente amable me mostró dos formas de obtener casi esa notación.
AndrewC
21

Como no tengo la ambición de mejorar la respuesta técnica de CA McCann , abordaré la más esponjosa:

Si pudieras cambiar el nombre purea algo más amigable para los podunks como yo, ¿cómo lo llamarías?

Como alternativa, especialmente porque no hay fin al constante llanto lleno de angustia y traición contra la Monadversión, llamada " return", propongo otro nombre, que sugiere su función de una manera que puede satisfacer el más imperativo de los programadores imperativos. , y el más funcional de ... bueno, es de esperar, todo el mundo puede quejarse sobre la misma: inject.

Toma un valor. "Inyectar" en el Functor, Applicative, Monad, o lo que usted quiera. Voto por " inject" y aprobé este mensaje.

BMeph
fuente
4
Por lo general, me inclino hacia algo como "unidad" o "elevación", pero esos ya tienen muchos otros significados en Haskell. injectes un nombre excelente y probablemente mejor que el mío, aunque como nota al margen menor, "inyectar" se usa en, creo, Smalltalk y Ruby para un método de plegado a la izquierda de algún tipo. Sin embargo, nunca entendí esa elección de nombre ...
CA McCann
3
Este es un hilo muy antiguo, pero creo que injecten Ruby y Smalltalk se usa porque es como si estuvieras "inyectando" un operador entre cada elemento de la lista. Al menos, así siempre lo pensé.
Jonathan Sterling
1
Para volver recoger ese antiguos lado de la rosca: Usted no es la inyección de los operadores, que está reemplazando (eliminación) de los constructores que ya están allí. (Visto al revés, está inyectando datos antiguos en un nuevo tipo). Para las listas, la eliminación es simplemente foldr. (Reemplaza (:)y [], donde (:)toma 2 argumentos y []es una constante, por foldr (+) 0 (1:2:3:[])lo tanto ↝ 1+2+3+0.) En Booles solo if- then- else(dos constantes, elija una) y para Maybeque se llame maybe... Haskell no tiene un solo nombre / función para esto, ya que todos tienen diferentes tipos (en general, la eliminación es solo recursión / inducción)
nadie
@CAMcCann Smalltalk obtuvo ese nombre de una canción de Arlo Guthrie sobre el borrador de la guerra de Vietnam, en la que se reunían, seleccionaban, a veces rechazaban y, de lo contrario, inyectaban a jóvenes desafortunados.
Tom Anderson
7

En breve:

  • <*>puede llamarlo aplicar . Por lo que Maybe f <*> Maybe ase puede pronunciar como aplicar Maybe fsobreMaybe a .

  • Puede cambiar el nombre purea of, como hacen muchas bibliotecas de JavaScript. En JS puede crear un archivo Maybecon Maybe.of(a).

Además, la wiki de Haskell tiene una página sobre la pronunciación de los operadores de idiomas aquí.

Marcelo Lazaroni
fuente
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Fuente: Programación de Haskell de First Principles , por Chris Allen y Julie Moronuki

dmvianna
fuente
Hasta donde yo sé, esos nombres no se han popularizado exactamente.
partir
@dfeuer espera a la próxima generación de Haskellers que estén usando ese libro como su material de aprendizaje principal.
dmvianna
1
Podría ocurrir. Sin embargo, los nombres son horribles, ya que no tienen conexión con los significados.
partir
1
@dfeuer, no veo una buena solución en ningún lado. "ap" / "apply" es tan vago como "tie fighter". Todo es aplicación de funciones. Sin embargo, un nombre que surge de la nada podría adquirir significado a través del uso. "Apple" es un buen ejemplo. Por cierto, Monad return es Applicative puro. Aquí no hay inventos.
dmvianna