haskell monads side-effects applicative

haskell - ¿Por qué los funtores aplicativos pueden tener efectos secundarios, pero los funtores no pueden?



monads side-effects (4)

Me siento bastante tonto al hacer esta pregunta, pero ha estado en mi mente por un tiempo y no puedo encontrar ninguna respuesta.

Entonces la pregunta es: ¿por qué los funtores aplicativos pueden tener efectos secundarios, pero los funtores no pueden?

Tal vez puedan y yo nunca me haya dado cuenta ...?


Esta respuesta es un poco simplista, pero si definimos los efectos secundarios como los cálculos que se ven afectados por los cálculos anteriores, es fácil ver que la clase de tipos de Functor es insuficiente para los efectos secundarios simplemente porque no hay manera de encadenar múltiples cálculos.

class Functor f where fmap :: (a -> b) -> f a -> f b

Lo único que puede hacer un functor es alterar el resultado final de un cálculo a través de alguna función pura a -> b .

Sin embargo, un functor aplicativo agrega dos funciones nuevas, pure y <*> .

class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b

La <*> es la diferencia crucial aquí, ya que nos permite encadenar dos cálculos: f (a -> b) (un cálculo que produce una función) y un cálculo que proporciona el parámetro al que se aplica la función. Usando pure y <*> es posible definir, por ejemplo,

(*>) :: f a -> f b -> f b

Que simplemente encadena dos cálculos, descartando el resultado final del primero (pero posiblemente aplicando "efectos secundarios").

En resumen, la capacidad de los cálculos en cadena es el requisito mínimo para efectos tales como el estado mutable en los cálculos.


No es cierto que los Functor no tengan efectos. Cada Applicative (y cada Monad través de WrappedMonad ) es un Functor . La principal diferencia es que Applicative y Monad brindan herramientas sobre cómo trabajar con esos efectos, cómo combinarlos. Aproximadamente

  • Applicative te permite secuenciar efectos y combinar valores dentro.
  • Además, la Monad te permite determinar el siguiente efecto según el resultado de uno anterior.

Sin embargo, Functor solo le permite modificar el valor interior, no proporciona herramientas para hacer nada con el efecto. Entonces, si algo es solo Functor y no Applicative , no significa que no tenga efectos. Simplemente no tiene un mecanismo para combinarlos de esta manera.

Actualización: Como ejemplo, considere

import Control.Applicative newtype MyF r a = MyF (IO (r, a)) instance Functor (MyF r) where fmap f (MyF x) = MyF $ fmap (fmap f) x

Esto es claramente una instancia de Functor que lleva efectos. Es solo que no tenemos una manera de definir las operaciones con estos efectos que cumplirían con Applicative . A menos que impongamos algunas restricciones adicionales a r , no hay forma de definir una instancia de Applicative .


Otras respuestas aquí han indicado con razón que los funtores no permiten los efectos secundarios porque no pueden combinarse o secuenciarse, lo cual es bastante cierto en general, pero hay una manera de secuenciar los funtores: ir hacia adentro.

Vamos a escribir un functor de escritor limitado.

data Color = R | G | B data ColorW a = Re a | Gr a | Bl a deriving (Functor)

y luego aplicar el tipo de mónada libre a él

data Free f a = Pure a | Free (f (Free f a)) liftF :: Functor f => f a -> Free f a liftF = Free . fmap Pure type ColorWriter = Free ColorW red, blue, green :: a -> ColorWriter a red = liftF . Re green = liftF . Gr blue = liftF . Bl

Por supuesto, por la propiedad libre, esto forma una mónada, pero los efectos realmente vienen de las "capas" del functor.

interpretColors :: ColorWriter a -> ([Color], a) interpretColors (Pure a) = ([], a) interpretColors (Free (Re next)) = let (colors, a) = interpretColors next in (R : colors, a) ...

Por lo tanto, esto es una especie de truco. Realmente el "cálculo" está siendo introducido por la mónada libre, pero el material de la computación, el contexto oculto, es introducido solo por un funtor. Resulta que puedes hacer esto con cualquier tipo de datos, ni siquiera tiene que ser un Functor, pero Functor proporciona una forma clara de construirlo.


Primero vamos a cambiar el nombre de los efectos secundarios a los efectos . Todo tipo de valores pueden tener efectos. Un functor es un tipo que le permite mapear una función sobre lo que sea producido por ese efecto.

Cuando un funtor no es aplicativo, no le permite usar un cierto estilo de composición para los efectos. Vamos a elegir un ejemplo (ideado):

data Contrived :: * -> * where AnInt :: Int -> Contrived Int ABool :: Bool -> Contrived Bool None :: Contrived a

Esto es fácilmente un functor:

instance Functor Contrived where fmap f (AnInt x) = AnInt (f x) fmap f (ABool x) = ABool (f x) fmap _ None = None

Sin embargo, no existe una implementación sensata para pure , por lo que este tipo no es un functor aplicativo. Es similar a Maybe porque tiene el efecto de que puede no haber un valor de resultado. Pero no se puede componer utilizando combinadores aplicativos.