Recientemente, comencé a aprender Haskell porque quería ampliar mis conocimientos sobre programación funcional y debo decir que hasta ahora me encanta. El recurso que estoy usando actualmente es el curso 'Haskell Fundamentals Part 1' en Pluralsight. Desafortunadamente, tengo algunas dificultades para entender una cita particular del profesor sobre el siguiente código y esperaba que ustedes pudieran arrojar algo de luz sobre el tema.
Código de acompañamiento
helloWorld :: IO ()
helloWorld = putStrLn "Hello World"
main :: IO ()
main = do
helloWorld
helloWorld
helloWorld
La frase
Si tiene la misma acción IO varias veces en un do-block, se ejecutará varias veces. Entonces, este programa imprime la cadena 'Hola Mundo' tres veces. Este ejemplo ayuda a ilustrar que putStrLn
no es una función con efectos secundarios. Llamamos a la putStrLn
función una vez para definir la helloWorld
variable. Si putStrLn
tuviera un efecto secundario de imprimir la cadena, solo se imprimiría una vez y la helloWorld
variable repetida en el bloque principal no tendría ningún efecto.
En la mayoría de los otros lenguajes de programación, un programa como este imprimiría 'Hello World' solo una vez, ya que la impresión ocurriría cuando putStrLn
se llamara a la función. Esta sutil distinción a menudo hace tropezar a los principiantes, así que piense un poco en esto y asegúrese de comprender por qué este programa imprime 'Hello World' tres veces y por qué lo imprimiría solo una vez si la putStrLn
función imprimiera como un efecto secundario.
Lo que no entiendo
Para mí parece casi natural que la cadena 'Hello World' se imprima tres veces. Percibo la helloWorld
variable (¿o función?) Como una especie de devolución de llamada que se invoca más tarde. Lo que no entiendo es cómo si putStrLn
tuviera un efecto secundario, la cadena se imprimiría solo una vez. O por qué solo se imprimiría una vez en otros lenguajes de programación.
Digamos que en el código C #, supongo que se vería así:
C # (violín)
using System;
public class Program
{
public static void HelloWorld()
{
Console.WriteLine("Hello World");
}
public static void Main()
{
HelloWorld();
HelloWorld();
HelloWorld();
}
}
Estoy seguro de que estoy pasando por alto algo bastante simple o malinterpreto su terminología. Cualquier ayuda sería muy apreciada.
EDITAR:
¡Gracias a todos por sus respuestas o comentarios! Sus respuestas me ayudaron a comprender mejor estos conceptos. No creo que haya hecho clic completamente todavía, pero volveré a visitar el tema en el futuro, ¡gracias!
helloWorld
ser constante, como un campo o variable en C #. No hay ningún parámetro al que se esté aplicandohelloWorld
.putStrLn
no tiene un efecto secundario; simplemente devuelve una acción IO, la misma acción IO para el argumento,"Hello World"
sin importar cuántas veces llameputStrLn
.helloworld
no sería una acción que imprimaHello world
; sería el valor devuelto porputStrLn
después de lo impresoHello World
(a saber,()
).helloWorld = Console.WriteLine("Hello World");
. Simplemente contiene elConsole.WriteLine("Hello World");
en laHelloWorld
función a ejecutar cada vez queHelloWorld
se invoca. Ahora piensa en lo quehelloWorld = putStrLn "Hello World"
hacehelloWorld
. Se asigna a una mónada IO que contiene()
. Una vez que lo vincula>>=
, solo realizará su actividad (imprimiendo algo) y le dará()
en el lado derecho del operador de vinculación.Respuestas:
Probablemente sería más fácil entender lo que quiere decir el autor si definimos
helloWorld
como una variable local:que puedes comparar con este pseudocódigo similar a C #:
Es decir, en C #
WriteLine
es un procedimiento que imprime su argumento y no devuelve nada. En Haskell,putStrLn
es una función que toma una cadena y le da una acción que imprimiría esa cadena si se ejecutara. Significa que no hay absolutamente ninguna diferencia entre escribiry
Dicho esto, en este ejemplo, la diferencia no es particularmente profunda, por lo que está bien si no entiendes a qué está tratando de llegar el autor en esta sección y simplemente continúas por ahora.
funciona un poco mejor si lo comparas con Python
El punto aquí es que las acciones de E / S en Haskell son valores "reales" que no necesitan estar envueltos en "devoluciones de llamada" adicionales ni nada por el estilo para evitar que se ejecuten; más bien, la única forma de hacer que se ejecuten es para ponerlos en un lugar particular (es decir, en algún lugar dentro
main
o se generó un hilomain
).Esto tampoco es solo un truco de salón, sino que termina teniendo algunos efectos interesantes sobre cómo escribir código (por ejemplo, es parte de la razón por la cual Haskell realmente no necesita ninguna de las estructuras de control comunes con las que estaría familiarizado) con lenguajes imperativos y puede salirse con la suya haciendo todo en términos de funciones en su lugar), pero nuevamente no me preocuparía demasiado por esto (analogías como estas no siempre hacen clic inmediatamente)
fuente
Puede ser más fácil ver la diferencia como se describe si usa una función que realmente hace algo, en lugar de hacerlo
helloWorld
. Piense en lo siguiente:Esto imprimirá "Estoy agregando 2 y 3" 3 veces.
En C #, puede escribir lo siguiente:
Que se imprimiría solo una vez.
fuente
Si la evaluación
putStrLn "Hello World"
tuviera efectos secundarios, entonces el mensaje solo se imprimiría una vez.Podemos aproximar ese escenario con el siguiente código:
unsafePerformIO
toma unaIO
acción y "olvida" que es unaIO
acción, desanimándola de la secuencia habitual impuesta por la composición de lasIO
acciones y dejando que el efecto tenga lugar (o no) de acuerdo con los caprichos de la evaluación perezosa.evaluate
toma un valor puro y garantiza que el valor se evalúe siempre que se evalúe laIO
acción resultante , lo que para nosotros será, porque se encuentra en el camino demain
. Lo estamos usando aquí para conectar la evaluación de algunos valores a la ejecución del programa.Este código solo imprime "Hello World" una vez. Tratamos
helloWorld
como un valor puro. Pero eso significa que se compartirá entre todas lasevaluate helloWorld
llamadas. ¿Y por qué no? Es un valor puro después de todo, ¿por qué volver a calcularlo innecesariamente? La primeraevaluate
acción "muestra" el efecto "oculto" y las acciones posteriores solo evalúan el resultado()
, lo que no causa más efectos.fuente
unsafePerformIO
en esta etapa de aprendizaje de Haskell. Tiene "inseguro" en el nombre por una razón, y no debe usarlo a menos que pueda (y lo haya hecho) considerar cuidadosamente las implicaciones de su uso en contexto. El código que danidiaz puso en la respuesta captura perfectamente el tipo de comportamiento poco intuitivo que puede resultar deunsafePerformIO
.Hay un detalle a tener en cuenta: llama a la
putStrLn
función solo una vez, mientras definehelloWorld
. Enmain
función, solo usa el valor de retorno de esoputStrLn "Hello, World"
tres veces.El profesor dice que la
putStrLn
llamada no tiene efectos secundarios y es cierto. Pero mire el tipo dehelloWorld
: es una acción de IO.putStrLn
solo lo crea para ti. Más tarde, encadena 3 de ellos con eldo
bloque para crear otra acción de E / S -main
. Más tarde, cuando ejecutas tu programa, esa acción se ejecutará, ahí es donde se encuentran los efectos secundarios.El mecanismo que se encuentra en la base de esto - mónadas . Este poderoso concepto le permite usar algunos efectos secundarios como imprimir en un lenguaje que no admite efectos secundarios directamente. Simplemente encadena algunas acciones, y esa cadena se ejecutará al inicio de su programa. Necesitará comprender ese concepto profundamente si quiere usar Haskell en serio.
fuente