La necesidad de puro en los solicitantes

19

Estoy aprendiendo los aplicantes de Haskell. Me parece (probablemente estoy equivocado) que la purefunción no es realmente necesaria, por ejemplo:

pure (+) <*> [1,2,3] <*> [3,4,5]

Se puede escribir como

(+) <$> [1,2,3] <*> [3,4,5]

¿Alguien puede explicar el beneficio que pureproporciona la función sobre el mapeo explícito fmap?

Gil Shafriri
fuente
1
Tienes razón: pure f <*> xes exactamente lo mismo que fmap f x. Estoy seguro de que hay alguna razón por la que purese incluyó Applicative, pero no estoy completamente seguro de por qué.
bradrn
44
No tengo tiempo para una respuesta, y no estoy convencido de que esto sea bueno o completo de todos modos, pero una observación: purepermite usar, bueno, valores "puros" en un cálculo aplicativo. Si bien, como se observa correctamente, pure f <*> xes el mismo que f <$> x, no hay tal equivalente para, por ejemplo, f <*> x <*> pure y <*> z. (Al menos no lo creo)
Robin Zigmond
3
Como otra justificación más teórica, hay una formulación alternativa que la relaciona estrechamente con la Monoidclase importante , en la que purecorresponde al Monoidelemento de identidad. (Esto sugiere que Applicativesin purepodría ser interesante, ya Semigroupque, que es un Monoidsin tener necesariamente una identidad, todavía se usa. En realidad, ahora que lo pienso, parece recordar que PureScript tiene exactamente esa pureclase de "Aplicativo sin ", aunque no no sé para qué se usa.)
Robin Zigmond
2
@RobinZigmond fmap (\f' x' z' -> f' x' y z') f <*> x <*> z, creo. La idea está en la Applicativedocumentación como la ley del "intercambio".
HTNW
3
@RobinZigmond Applicativesin pureexiste a partir Applyde semigroupoids .
Duplode

Respuestas:

8

Estoy al borde de mi competencia aquí, así que no tome esto por más de lo que es, pero fue demasiado largo para un comentario.

Puede haber razones prácticas para incluir pureen la clase de tipo, pero muchas abstracciones de Haskell se derivan de fundamentos teóricos, y creo que ese es el caso Applicativetambién. Como dice la documentación, es un functor monoidal fuerte y laxo (consulte https://cstheory.stackexchange.com/q/12412/56098 para obtener más detalles). Supongo que puresirve como identidad , al igual que lo returnhace para Monad(que es un monoide en la categoría de endofunctores ).

Considere purey liftA2:

pure :: a -> f a
liftA2 :: (a -> b -> c) -> f a -> f b -> f c

Si entrecierra un poco los ojos, puede imaginarse que liftA2es una operación binaria, que también es lo que dice la documentación:

Levante una función binaria a acciones.

pure, entonces, es la identidad correspondiente.

Mark Seemann
fuente
66
Exactamente. Applicativesin puresería un, hm, semigroupal functor en lugar de uno monoidal.
izquierda alrededor del
20

fmapno siempre lo corta Específicamente, purees lo que te permite introducir f(dónde festá Applicative) cuando aún no lo tienes. Un buen ejemplo es

sequence :: Applicative f => [f a] -> f [a]

Toma una lista de "acciones" que producen valores y la convierte en una acción que produce una lista de valores. ¿Qué sucede cuando no hay acciones en la lista? El único resultado sano es una acción que no produce valores:

sequence [] = pure [] -- no way to express this with an fmap
-- for completeness
sequence ((:) x xs) = (:) <$> x <*> sequence xs

Si no lo hubiera hecho pure, se vería obligado a requerir una lista de acciones no vacía. Definitivamente podría hacerlo funcionar, pero es como hablar de la suma sin mencionar 0 o la multiplicación sin 1 (como han dicho otros, porque los Applicatives son monoidales). En repetidas ocasiones se encontrará con casos extremos que se resolverían fácilmente purepero que, en cambio, deben resolverse mediante restricciones extrañas en sus entradas y otras curitas.

HTNW
fuente