¿Qué importancia tienen los conceptos avanzados de Haskell como Mónadas y Functores Aplicativos para la mayoría de las tareas de programación de rutina?

16

He leído el libro Learn You a Haskell hasta el punto en que presentan Mónadas y cosas como Just a. Estos son tan contraintuitivos para mí que me dan ganas de dejar de intentar aprenderlo.

Pero realmente quiero intentarlo.

Me pregunto si al menos puedo evitar los conceptos avanzados por un tiempo y simplemente comenzar a usar las otras partes del lenguaje para hacer muchas tareas prácticas con las partes más básicas de Haskell, es decir, funciones, E / S estándar, manejo de archivos, interfaz de base de datos y similares.

¿Es este un enfoque razonable para aprender a usar Haskell?

dan
fuente
2
No sé hasta dónde llegarás. Sin mónadas, no puedes hacer nada útil en Haskell, ya que hacer algo útil en la programación (como escribir un programa serio que alguien realmente quiera usar) requiere E / S y estado, los cuales solo se pueden administrar en Haskell a través del uso de mónadas.
Mason Wheeler
1
@dan: finalmente entendí a las mónadas de Haskell al leer dos documentos: "Podrías haber inventado mónadas" y "Tackling the Awkward Squad". No puedo decir si son mejores que "Learn You a Haskell", que no he leído, pero funcionaron para mí. Para usar la notación do con la mónada IO, puede pasar con muy poca comprensión de lo que son las mónadas: hará algunas cosas que harán que los expertos se rían o lloren, pero al menos superficialmente, no es muy diferente de usar Un lenguaje imperativo convencional. Pero es que vale la pena conseguir un poco de comprensión más profunda.
Steve314
2
@dan, me llevó casi una década aburrirme de la POO y comenzar a apreciar / sentir curiosidad por los lenguajes funcionales. Todavía aprendería F # y Clojure antes de darle un intento serio a Haskell. Si bien los desafíos personales son agradables, hay algo que decir sobre su instinto y el camino de menor resistencia. Mucho depende de quién eres. Si usted es un tipo fuertemente mathy, entonces probablemente le gustaría Haskell / Schem desde el principio. Si eres más un tipo de "hazlo", también está bien. No te acerques a Haskell con una actitud de "hazlo o muere". Sigue aprendiendo otras cosas, y cuando estés aburrido regresa.
Trabajo
Gracias por el consejo @Job. He intentado Clojure por un tiempo, pero la sintaxis de Lisp realmente no funciona para mí, tanto para leer como para escribir. Encuentro la sintaxis de Haskell más natural. Mientras tanto, estoy contento de usar Ruby para proyectos, pero espero comenzar a hacer algunos proyectos prácticos en Haskell.
dan
@dan, Divertido, encuentro que la sintaxis de Clojure es bastante amigable, pero eso es probablemente porque ya estuve expuesto a Scheme antes. Quizás después de F # me acostumbre a la forma en que Haskell hace las cosas.
Trabajo

Respuestas:

16

Primero distingamos entre aprender los conceptos abstractos y aprender ejemplos específicos de ellos.

No vas a llegar muy lejos ignorando todos los ejemplos específicos, por la simple razón de que son completamente ubicuos. De hecho, las abstracciones existen en gran parte porque unifican las cosas que estaría haciendo de todos modos con los ejemplos específicos.

Las abstracciones en sí mismas, por otro lado, son ciertamente útiles , pero no son inmediatamente necesarias. Puedes llegar bastante lejos ignorando por completo las abstracciones y simplemente usando los diferentes tipos directamente. Eventualmente querrás entenderlos, pero siempre puedes volver a ello más tarde. De hecho, casi puedo garantizar que si haces eso, cuando vuelvas a hacerlo, te darás una palmada en la frente y te preguntarás por qué pasaste todo ese tiempo haciendo las cosas de la manera más difícil en lugar de usar las prácticas herramientas de uso general.

Toma Maybe acomo ejemplo. Es solo un tipo de datos:

data Maybe a = Just a | Nothing

Es todo menos autodocumentado; Es un valor opcional. O tienes "solo" algo de tipo ao no tienes nada. Digamos que tiene una función de búsqueda de algún tipo, que vuelve Maybe Stringa representar la búsqueda de un Stringvalor que puede no estar presente. Entonces, el patrón coincide en el valor para ver cuál es:

case lookupFunc key of
    Just val -> ...
    Nothing  -> ...

¡Eso es todo!

Realmente, no hay nada más que necesites. No Functorso Monads ni nada más. Esos expresan formas comunes de usar Maybe avalores ... pero son solo expresiones idiomáticas, "patrones de diseño", como quieran llamarlo.

El único lugar en el que realmente no puedes evitarlo es IO, pero de todos modos es una misteriosa caja negra, por lo que no vale la pena tratar de entender lo que significa como Monado lo que sea.

De hecho, aquí hay una hoja de trucos para todo lo que realmente necesita saber IOpor ahora:

  • Si algo tiene un tipo IO a, eso significa que es un procedimiento que hace algo y escupe un avalor.

  • Cuando tienes un bloque de código usando la donotación, escribe algo como esto:

    do -- ...
       inp <- getLine
       -- etc...
    

    ... significa ejecutar el procedimiento a la derecha de <-, y asignar el resultado al nombre a la izquierda.

  • Mientras que si tienes algo como esto:

    do -- ...
       let x = [foo, bar]
       -- etc...
    

    ... significa asignar el valor de la expresión simple (no un procedimiento) a la derecha del =nombre a la izquierda.

  • Si coloca algo allí sin asignar un valor, así:

    do putStrLn "blah blah, fishcakes"
    

    ... significa ejecutar un procedimiento e ignorar todo lo que devuelve. Algunos procedimientos tienen el tipo IO ()--el ()tipo es una especie de marcador de posición que no dice nada, por lo que simplemente significa que el procedimiento hace algo y no devuelve un valor. Algo así como una voidfunción en otros idiomas.

  • Ejecutar el mismo procedimiento más de una vez puede dar resultados diferentes; Esa es la idea. Es por eso que no hay forma de "eliminar" el IOvalor de un valor, porque algo en IOno es un valor, es un procedimiento para obtener un valor.

  • La última línea en un dobloque debe ser un procedimiento simple sin asignación, donde el valor de retorno de ese procedimiento se convierte en el valor de retorno para todo el bloque. Si desea que el valor de retorno use algún valor ya asignado, la returnfunción toma un valor simple y le brinda un procedimiento sin operación que devuelve ese valor.

  • Aparte de eso, no tiene nada de especial IO; estos procedimientos son en realidad valores simples en sí mismos, y puede pasarlos y combinarlos de diferentes maneras. Es solo cuando se ejecutan en un dobloque llamado en algún lugar mainque hacen algo.

Entonces, en algo como este programa de ejemplo completamente aburrido y estereotipado:

hello = do putStrLn "What's your name?"
           name <- getLine
           let msg = "Hi, " ++ name ++ "!"
           putStrLn msg
           return name

... puedes leerlo como un programa imprescindible. Estamos definiendo un procedimiento llamado hello. Cuando se ejecuta, primero ejecuta un procedimiento para imprimir un mensaje preguntando su nombre; a continuación, ejecuta un procedimiento que lee una línea de entrada y asigna el resultado a name; entonces asigna una expresión al nombre msg; luego imprime el mensaje; luego devuelve el nombre del usuario como resultado de todo el bloque. Como namees a String, esto significa que helloes un procedimiento que devuelve a String, por lo que tiene tipo IO String. Y ahora puede ejecutar este procedimiento en otro lugar, tal como se ejecuta getLine.

Pfff, mónadas. ¿Quién los necesita?

CA McCann
fuente
2
@dan: ¡De nada! Si tiene alguna pregunta específica en el camino, no dude en preguntar en Stack Overflow. La etiqueta [haskell] suele ser bastante activa, y si explica de dónde viene, las personas son bastante buenas para ayudarlo a obtener comprensión en lugar de simplemente arrojar respuestas crípticas.
CA McCann
9

¿Qué importancia tienen los conceptos avanzados de Haskell como Mónadas y Functores Aplicativos para la mayoría de las tareas de programación de rutina?

Son esenciales. Sin ellos, es demasiado fácil usar Haskell como otro lenguaje imperativo, y aunque Simon Peyton Jones una vez llamó a Haskell, "el mejor lenguaje de programación imperativo del mundo", todavía te estás perdiendo la mejor parte.

Mónadas, transformadas de mónada, monoides, functores, fundores aplicativos, functores contravalentes, flechas, categorías, etc., son patrones de diseño. Una vez que se ajusta lo específico que está haciendo en uno de ellos, puede recurrir a una gran cantidad de funciones que se escriben de manera abstracta. Estas clases de tipos no están simplemente ahí para confundirte, están ahí para hacerte la vida más fácil y para hacerte más productivo. Son, en mi opinión, la razón más importante para la concisión del buen código Haskell.

dan_waterworth
fuente
1
+1: Gracias por señalar que "Mónadas, transformadas de mónada, monoides, functores, functores aplicativos, functores contravalentes, flechas, categorías, etc., son patrones de diseño". !
Giorgio
7

¿Es estrictamente necesario si solo quieres que algo funcione? No.

¿Es necesario si quieres escribir hermosos programas idiomáticos de Haskell? Absolutamente.

Al programar en Haskell, ayuda tener dos cosas en mente:

  1. El sistema de tipos está ahí para ayudarlo. Si compone su programa a partir de un código altamente polimórfico de clase de tipo pesado, muy a menudo puede entrar en la maravillosa situación en la que el tipo de función que desea lo guiará a la implementación correcta (¡una sola!) .

  2. Las diversas bibliotecas disponibles se desarrollaron utilizando el principio n. ° 1.

Entonces, cuando escribo un programa en Haskell, empiezo escribiendo los tipos. Luego empiezo de nuevo y escribo algunos tipos más generales (y si pueden ser instancias de clases de tipos existentes, mucho mejor). Luego escribo algunas funciones que me dan valores de los tipos que quiero. (Luego juego con ellos ghciy tal vez escribo algunas pruebas de comprobación rápida). Y finalmente lo pego todo main.

Es posible escribir un feo Haskell que solo hace que algo funcione. Pero hay mucho más que aprender una vez que haya alcanzado esa etapa.

¡Buena suerte!

Lambdageek
fuente
1
¡Gracias! He estado dudando entre Clojure y Haskell, pero ahora me estoy inclinando hacia Haskell debido a lo serviciales que han sido las personas como usted.
dan
1
La comunidad de Haskell parece ser muy amable y servicial. Y parece ser de donde provienen muchas ideas interesantes
Zachary K