haskell monads applicative side-effects

haskell - ¿Qué significa exactamente "eficaz"



monads applicative (4)

Una y otra vez leo el término efectivo , pero todavía no puedo dar una definición clara de lo que significa. Supongo que el contexto correcto son cálculos efectivos, pero también he visto el término values efectivos)

Solía ​​pensar que eficaz significa tener efectos secundarios . Pero en Haskell no hay efectos secundarios (excepto en cierta medida IO). Todavía hay cálculos efectivos en todo el lugar.

Luego leí que las mónadas se utilizan para crear cálculos efectivos. Puedo entender algo de esto en el contexto de la Mónada State . Pero no veo ningún efecto secundario en la mónada Maybe . En general, me parece que las Mónadas que envuelven una cosa parecida a una función son más fáciles de ver que producen efectos secundarios que las Mónadas que solo envuelven un valor.

Cuando se trata de funtores Applicative , estoy aún más perdido. Siempre vi los funtores aplicativos como una forma de map una función con más de un argumento. No puedo ver ningún efecto secundario aquí. ¿O hay una diferencia entre eficaz y con efectos ?


"Efecto es un término muy vago y está bien porque estamos tratando de hablar sobre algo que está fuera del lenguaje. Efecto y efecto secundario no son lo mismo. Los efectos son buenos. Los efectos secundarios son errores".

"Su similitud léxica es realmente desafortunada porque lleva a muchas personas a mezclar estas ideas cuando leen sobre ellas y personas que usan una en lugar de la otra, lo que lleva a mucha confusión".

Consulte aquí para obtener más información: https://www.slideshare.net/pjschwarz/rob-norrisfunctionalprogrammingwitheffects


En apoyo de la respuesta de Petr Pudlák , aquí hay un argumento sobre el origen de la noción más amplia de "efecto" que se adhiere allí.

La frase "programación efectiva" aparece en el resumen de la Programación aplicativa con efectos de McBride y Patterson, el artículo que introdujo los funtores aplicativos:

En este artículo, presentamos los funtores Applicative : una caracterización abstracta de un estilo aplicativo de programación efectiva, más débil que la de la Monad y, por lo tanto, más extendida.

"Efecto" y "efectivo" aparecen en un puñado de otros pasajes del artículo; estas ocurrencias se consideran lo suficientemente interesantes como para no requerir una aclaración explícita. Por ejemplo, esta observación se hace justo después de que se presenta la definición de Applicative (p. 3):

En cada ejemplo, hay un constructor tipo f que incorpora la noción habitual de valor, pero apoya su propia forma peculiar de dar sentido al lenguaje aplicativo habitual. [...] Por consiguiente, introducimos la clase Applicative :

[Una definición de Haskell de Applicative ]

Esta clase generaliza S y K [es decir, los combinadores S y K , que aparecen en la instancia del Applicative Reader / function] desde enhebrar un entorno para enhebrar un efecto en general.

De estas citas, podemos inferir que, en este contexto:

  • Los efectos son las cosas que los hilos Applicative "en general".

  • Los efectos están asociados con los constructores de tipo que se dan instancias de Applicative .

  • Monad también se ocupa de los efectos.

Siguiendo estos indicios, podemos rastrear este uso del "efecto" hasta al menos los documentos de Wadler sobre las mónadas . Por ejemplo, aquí hay una cita de la página 6 de Monads para la programación funcional :

En general, una función de tipo a → b se reemplaza por una función de tipo a → M b . Esto se puede leer como una función que acepta un argumento de tipo a y devuelve un resultado de tipo b , con un posible efecto adicional capturado por M. Este efecto puede ser actuar sobre el estado, generar resultados, generar una excepción o lo que sea.

Y del mismo papel, página 21:

Si las mónadas encapsulan los efectos y las listas forman una mónada, ¿las listas corresponden a algún efecto? De hecho lo hacen, y el efecto al que corresponden es la elección. Uno puede pensar que un cálculo de tipo [a] ofrece una selección de valores, uno para cada elemento de la lista. El equivalente monádico de una función de tipo a → b es una función de tipo a → [b] .

El giro de la frase "corresponde a algún efecto" aquí es clave. Se enlaza con la afirmación más sencilla en abstracto:

Las mónadas proporcionan un marco conveniente para simular los efectos encontrados en otros idiomas, como el estado global, el manejo de excepciones, la salida o el no determinismo.

El tono es que las mónadas se pueden usar para expresar cosas que, en "otros idiomas", normalmente se codifican como efectos secundarios, es decir, como lo expresa Petr Pudlák en su respuesta aquí, "una interacción observable con [una función] medio ambiente (aparte de calcular su valor de resultado) ". A través de la metonimia, eso ha conducido fácilmente a que el "efecto" adquiera un segundo significado, más amplio que el de "efecto secundario", es decir, todo lo que se introduce a través de un constructor de tipos que es una instancia de Monad . Con el tiempo, este significado se generalizó aún más para abarcar otras clases de funtores como Applicative , como se ve en el trabajo de McBride y Patterson.

En resumen, considero que "efecto" tiene dos significados razonables en el lenguaje de Haskell:

  • Un "literal" o "absoluto": un efecto es un efecto secundario; y

  • Una "generalizada" o "relativa": un efecto es un contexto funcional.

En ocasiones, los desacuerdos evitables sobre la terminología ocurren cuando cada una de las partes involucradas asume implícitamente un significado diferente de "efecto". Otro posible punto de discusión es si es legítimo hablar de efectos cuando se trata solo de Functor , a diferencia de subclases como el Applicative o la Monad (creo que está bien hacerlo, de acuerdo con la respuesta de Petr Pudlák a ¿Por qué los funtores aplicativos pueden hacerlo ?) tienen efectos secundarios, pero los funtores no pueden? ).


En mi opinión, un "efecto secundario" es algo que una función normal no podía hacer. En otras palabras, cualquier cosa además de simplemente devolver un valor.

Considere el siguiente bloque de código:

let y = foo x z = bar y in foobar z

Esto llama a foo , y luego llama a la bar , y luego llama a foobar , tres funciones ordinarias. Bastante simple, ¿verdad? Ahora considera esto:

do y <- foo x z <- bar y foobar z

Esto también llama a tres funciones, pero también invisiblemente llama (>>=) entre cada par de líneas. Y eso significa que suceden algunas cosas extrañas, según el tipo de mónada en la que se ejecutan las funciones.

  • Si esta es la mónada de identidad, no pasa nada especial. La versión monádica hace exactamente lo mismo que la versión pura. No hay efectos secundarios.

  • Si cada función devuelve un elemento Maybe , entonces si la bar (por ejemplo) devuelve Nothing , el bloque de código completo se cancela. Una función normal no puede hacer eso. (Es decir, en la versión pura, no hay manera de evitar que se llame a foobar ). Por lo tanto, esta versión hace algo que la versión pura no puede hacer. Cada función puede devolver un valor o abortar el bloque . Eso es un efecto secundario.

  • Si cada función devuelve una lista de cosas, entonces el código se ejecuta para todas las combinaciones posibles de resultados. Nuevamente, en la versión pura, no hay manera de hacer que ninguna de las funciones se ejecute varias veces con diferentes argumentos. Así que eso es un efecto secundario.

  • Si cada función se ejecuta en una mónada de estado, (por ejemplo) foo puede enviar algunos datos directamente a foobar , además del valor que puede ver que se pasa a través de la bar . Nuevamente, no puedes hacer eso con funciones puras, así que eso es un efecto secundario.

  • En IO monad, tienes todo tipo de efectos interesantes. Puede guardar archivos en el disco (un archivo es básicamente una variable global gigante) e incluso puede afectar el código que se ejecuta en otras computadoras (a esto le llamamos E / S de red).

  • La mónada ST es una versión reducida de la mónada IO . Permite un estado mutable, pero los cálculos autocontenidos no pueden influirse entre sí.

  • La mónada STM permite que varios subprocesos se comuniquen entre sí y puede hacer que el código se ejecute varias veces, y ... bueno, no puede hacer nada de esto con las funciones normales.

  • La mónada de continuación le permite romper las mentes de las personas! Podría decirse que es posible con funciones puras ...


Un efecto secundario es una interacción observable con su entorno (aparte de calcular su valor de resultado). En Haskell, nos esforzamos por evitar funciones con tales efectos secundarios. Esto se aplica incluso a las acciones IO : cuando se evalúa una acción IO , no se realizan efectos secundarios, se ejecutan solo cuando las acciones prescritas en el valor IO se ejecutan dentro de main .

Sin embargo, cuando se trabaja con abstracciones relacionadas con la composición de cálculos, como funtores aplicativos y mónadas, es conveniente distinguir algo entre el valor real y el "resto", que a menudo llamamos un "efecto". En particular, si tenemos un tipo f del kind * -> * , entonces, en fa la parte es "el valor" y lo que "permanece" es "el efecto".

Cité intencionalmente los términos, ya que no hay una definición precisa (que yo sepa), es simplemente una definición coloquial. En algunos casos no hay valores en absoluto, o valores múltiples. Por ejemplo, para Maybe el "efecto" es que puede que no haya ningún valor (y el cálculo se cancela), para [] el "efecto" es que hay valores múltiples (o cero). Para tipos más complejos, esta distinción puede ser aún más difícil.

La distinción entre "efectos" y "valores" no depende realmente de la abstracción. Functor , Applicative y Monad solo nos proporcionan herramientas sobre lo que podemos hacer con ellos (los Functor permiten modificar los valores internos, los Applicative permiten combinar efectos y los Monad permiten que los efectos dependan de los valores anteriores). Pero en el contexto de Monad s, es algo más fácil crear una imagen mental de lo que está sucediendo, porque una acción monádica puede "ver" el valor del resultado del cálculo anterior, como lo demuestra el

(>>=) :: m a -> (a -> m b) -> m b

operador: La segunda función recibe un valor de tipo a , por lo que podemos imaginar que "el cálculo anterior tuvo algún efecto y ahora está su valor de resultado con el que podemos hacer algo".