Haskell, Lisp y verbosidad [cerrado]

104

Para aquellos de ustedes con experiencia tanto en Haskell como en Lisp, tengo curiosidad por saber cuán "agradable" (para usar un término horrible) es escribir código en Haskell vs. Lisp.

Algunos antecedentes: estoy aprendiendo Haskell ahora, habiendo trabajado anteriormente con Scheme y CL (y una pequeña incursión en Clojure). Tradicionalmente, podrías considerarme un fanático de los lenguajes dinámicos por la brevedad y rapidez que brindan. Rápidamente me enamoré de las macros Lisp, ya que me dieron otra forma de evitar la verbosidad y la repetición.

Haskell me parece increíblemente interesante, ya que me presenta formas de codificación que no sabía que existían. Definitivamente tiene algunos aspectos que parecen ayudar a lograr la agilidad, como la facilidad para escribir funciones parciales. Sin embargo, estoy un poco preocupado por perder las macros Lisp (supongo que las pierdo; la verdad es que es posible que aún no las haya aprendido) y el sistema de escritura estática.

¿A alguien que haya hecho una cantidad decente de codificación en ambos mundos le importaría comentar cómo difieren las experiencias, cuál prefiere y si dicha preferencia es situacional?

J Cooper
fuente

Respuestas:

69

Respuesta corta:

  • Casi todo lo que puede hacer con macros lo puede hacer con una función de orden superior (e incluyo mónadas, flechas, etc.), pero puede requerir más pensamiento (pero solo la primera vez, y es divertido y usted será un mejor programador para ello), y
  • el sistema estático es lo suficientemente general como para que nunca se interponga en su camino, y de manera algo sorprendente en realidad "ayuda a lograr agilidad" (como usted dijo) porque cuando su programa se compila puede estar casi seguro de que es correcto, por lo que esta certeza le permite probar cosas que de otro modo tendrías miedo de probar; hay una sensación "dinámica" en la programación, aunque no es lo mismo que con Lisp.

[Nota: Hay una " Plantilla Haskell " que le permite escribir macros como en Lisp, pero estrictamente hablando, nunca debería necesitarla .]

ShreevatsaR
fuente
15
De Conor McBride, citado por Don Stewart: 'Me gusta pensar que los tipos deforman nuestra gravedad, de modo que la dirección en la que necesitamos viajar [para escribir programas correctos] se vuelve "cuesta abajo". El sistema de tipos hace que sea sorprendentemente fácil escribir programas correctos… vea esta publicación y sus re-compartidos.
ShreevatsaR
3
Las funciones de orden superior no pueden reemplazar las macros y, de hecho, CL tiene ambas por alguna razón. El verdadero poder de las macros en CL es que permiten al desarrollador introducir nuevas características de lenguaje que ayudan a expresar mejor la solución a un problema, sin tener que esperar una nueva versión del lenguaje como en Haskell o Java. Por ejemplo, si Haskell tuviera este poder, no sería necesario que los autores de Haskell escribieran extensiones GHC, ya que los mismos desarrolladores podrían implementarlas como macros en cualquier momento.
mljrg
@mljrg ¿Tiene un ejemplo concreto? Vea los comentarios sobre la respuesta de Hibou57 a continuación, donde un supuesto ejemplo resultó ser dudoso. Me interesaría saber a qué te refieres (por ejemplo, código Haskell con y sin macros).
ShreevatsaR
4
Saque el curry de Haskell. ¿Podrías implementarlo con lo que quedaría en Haskell? Otro ejemplo: suponga que Haskell no admite la coincidencia de patrones, ¿podría agregarlo usted mismo sin que los desarrolladores de GHC lo admitan? En CL, puede utilizar macros para ampliar el idioma a voluntad. Supongo que es por eso que CL el lenguaje no cambió desde su estándar en los años 90, mientras que Haskell parece tener un flujo interminable de extensiones en GHC.
mljrg
64

En primer lugar, no se preocupe por perder características particulares como la escritura dinámica. Como está familiarizado con Common Lisp, un lenguaje notablemente bien diseñado, supongo que es consciente de que un lenguaje no se puede reducir a su conjunto de características. Se trata de un todo coherente, ¿no?

En este sentido, Haskell brilla tan intensamente como Common Lisp. Sus características se combinan para proporcionarle una forma de programación que hace que el código sea extremadamente corto y elegante. La falta de macros se mitiga un poco con conceptos más elaborados (pero, igualmente, más difíciles de entender y usar) como mónadas y flechas. El sistema de tipo estático aumenta su poder en lugar de interponerse en su camino como lo hace en la mayoría de los lenguajes orientados a objetos.

Por otro lado, la programación en Haskell es mucho menos interactiva que Lisp, y la enorme cantidad de reflexión presente en lenguajes como Lisp simplemente no encaja con la visión estática del mundo que presupone Haskell. Por lo tanto, los conjuntos de herramientas disponibles son bastante diferentes entre los dos idiomas, pero difíciles de comparar entre sí.

Personalmente, prefiero la forma de programación Lisp en general, ya que creo que encaja mejor con la forma en que trabajo. Sin embargo, esto no significa que esté obligado a hacerlo también.

Matthias Benkard
fuente
4
¿Podría desarrollar un poco más sobre "la programación en Haskell es mucho menos interactiva"? ¿GHCi realmente no proporciona todo lo que necesita?
Johannes Gerer
3
@JohannesGerer: No lo he probado, pero por lo que he leído, GHCi no es un shell en la imagen en ejecución, donde puede redefinir y extender partes arbitrarias de todo el programa mientras se está ejecutando. Además, la sintaxis de Haskell hace que sea mucho más difícil copiar fragmentos de programa entre la respuesta y el editor mediante programación.
Svante
13

Hay menos necesidad de metaprogramación en Haskell que en Common Lisp porque mucho se puede estructurar alrededor de mónadas y la sintaxis agregada hace que los DSL integrados se vean menos como árboles, pero siempre existe Template Haskell, como lo menciona ShreevatsaR , e incluso Liskell (Haskell semántica + Lisp sintaxis) si le gustan los paréntesis.

Herrmann
fuente
1
el enlace Liskell está muerto, pero en estos días está Hackett .
Will Ness
10

En cuanto a las macros, aquí hay una página en la que se habla de ello: Hola Haskell, Adiós Lisp . Explica un punto de vista donde las macros simplemente no son necesarias en Haskell. Viene con un pequeño ejemplo de comparación.

Caso de ejemplo en el que se requiere una macro LISP para evitar la evaluación de ambos argumentos:

(defmacro doif (x y) `(if ,x ,y))

Caso de ejemplo en el que Haskell no evalúa sistemáticamente ambos argumentos, sin la necesidad de nada como una definición de macro:

doif x y = if x then (Just y) else Nothing

Y voilá

Hibou57
fuente
17
Ese es un error común. Sí, en Haskell la pereza significa que no necesita macros cuando quiere evitar evaluar algunas partes de una expresión, pero esos son solo el subconjunto más trivial de todos los usos de macro. Google para "The Swine Before Perl" para una charla que demuestra una macro que no se puede hacer con pereza. Además, si usted no quiere un poco de bits a ser estricto, entonces usted no puede hacer eso como una función - que refleja el hecho de que el Esquema de delayno puede ser una función.
Eli Barzilay
8
@Eli Barzilay: No encuentro este ejemplo muy convincente. Aquí hay una traducción completa y simple de Haskell de la diapositiva 40: pastebin.com/8rFYwTrE
Reid Barton
5
@Eli Barzilay: No entiendo tu respuesta en absoluto. accept es el (E) DSL. La acceptfunción es análoga a la macro descrita en las páginas anteriores, y la definición de ves exactamente paralela a la definición de vScheme en la diapositiva 40. Las funciones Haskell y Scheme calculan lo mismo con la misma estrategia de evaluación. En el mejor de los casos, la macro le permite exponer más de la estructura de su programa al optimizador. Difícilmente se puede afirmar esto como un ejemplo en el que las macros aumentan el poder expresivo del lenguaje de una manera que no se replica en una evaluación perezosa.
Reid Barton
5
@Eli Barzilay: En un esquema perezoso hipotético, podrías escribir esto: pastebin.com/TN3F8VVE Mi afirmación general es que esta macro te compra muy poco: sintaxis ligeramente diferente y un tiempo más fácil para el optimizador (pero no importaría un "compilador suficientemente inteligente"). A cambio, te has atrapado en un lenguaje inexpresivo; ¿Cómo se define un autómata que coincide con cualquier letra sin enumerarlas todas? Además, no sé qué quiere decir con "usarlo en todas las sublistas" o "el uso requerido de where con su propio alcance".
Reid Barton
15
Esta bien me rindo. Aparentemente, su definición de DSL es "los argumentos de una macro", por lo que mi ejemplo perezoso de Scheme no es un DSL, a pesar de ser sintácticamente isomorfo al original ( automatonconvertirse letrec, :convertirse accept, ->convertirse en nada en esta versión). Lo que sea.
Reid Barton
9

Soy programador de Common Lisp.

Después de haber probado Haskell hace algún tiempo, mi resultado final personal era seguir con CL.

Razones:

  • mecanografía dinámica (consulte la escritura dinámica frente a la estática: un análisis basado en patrones de Pascal Costanza )
  • argumentos opcionales y de palabra clave
  • sintaxis uniforme de lista homoicónica con macros
  • sintaxis de prefijo (no es necesario recordar las reglas de precedencia)
  • impuro y, por lo tanto, más adecuado para la creación rápida de prototipos
  • potente sistema de objetos con protocolo de metaobjetos
  • estándar maduro
  • amplia gama de compiladores

Haskell tiene sus propios méritos, por supuesto, y hace algunas cosas de una manera fundamentalmente diferente, pero para mí no es suficiente a largo plazo.

Leslie P. Polzer
fuente
Oye, ¿tienes el título de ese periódico de Costanza al que vinculaste? Parece que ese archivo se movió.
michiakig
2
Tenga en cuenta que haskell también admite la sintaxis de prefijo, pero yo diría que monad >> = sería muy, muy feo usarlo. También no estoy de acuerdo con que la impureza sea una bendición: P
alternativa
2
Me gusta esta nota al margen: todavía no hemos recopilado datos empíricos sobre si este problema causa problemas graves en los programas del mundo real.
Jerome Baum
22
Ninguno de los ejemplos de ese documento (Pascal Costanza, Escritura dinámica frente a escritura estática: un análisis basado en patrones ) se aplica a Haskell. Todos son específicos de Java (o más precisamente, específicos de la "programación orientada a objetos" ) y no veo que ninguno de esos problemas surja en Haskell. De manera similar, todos sus otros argumentos son discutibles: también se puede decir que Haskell es "puro y, por lo tanto, más adecuado para la creación rápida de prototipos", que la sintaxis de prefijo no es obligatoria, que no tiene una amplia gama de compiladores que hagan cosas diferentes. , etc.
ShreevatsaR
11
De hecho, ese documento es casi completamente irrelevante para Haskell. " dilbert = dogbert.hire(dilbert);" ?? Dudo que muchos programadores de Haskell puedan siquiera leer esto sin moverse un poco.
izquierda rotonda alrededor del
6

En Haskell puede definir una función if, lo cual es imposible en LISP. Esto es posible debido a la pereza, que permite una mayor modularidad en los programas. Este artículo clásico: Por qué es importante la FP de John Hughes, explica cómo la pereza mejora la composibilidad.

fuente
fuente
5
Scheme (uno de los dos dialectos principales de LISP) en realidad tiene una evaluación perezosa, aunque no es predeterminado como en Haskell.
Randy Voet
7
(defmacro doif (xy) `(if, x, y))
Joshua Cheek
4
Una macro no es lo mismo que una función: las macros no funcionan bien con funciones de orden superior como fold, por ejemplo, mientras que las funciones no estrictas sí lo hacen .
Tikhon Jelvis
5

Hay cosas realmente interesantes que puedes lograr en Lisp con macros que son engorrosas (si es posible) en Haskell. Tomemos, por ejemplo, la macro "memoize" (véase el capítulo 9 del PAIP de Peter Norvig). Con él, puede definir una función, digamos foo, y luego simplemente evaluar (memoize 'foo), que reemplaza la definición global de foo con una versión memoizada. ¿Puede lograr el mismo efecto en Haskell con funciones de orden superior?


fuente
3
No del todo (AFAIK), pero puede hacer algo similar modificando la función (asumiendo que es recursiva) para tomar la función para llamar de forma recursiva como un parámetro (!) En lugar de simplemente llamarse a sí mismo por su nombre: haskell.org/haskellwiki/Memoization
j_random_hacker
6
Puede agregar foo a una estructura de datos perezosa, donde el valor se almacenará una vez calculado. Esto será efectivamente el mismo.
Theo Belaire
Todo en Haskell está memorizado y probablemente insertado cuando el compilador de Haskell lo necesita de forma predeterminada.
aoeu256
4

A medida que continúo mi viaje de aprendizaje de Haskell, parece que una cosa que ayuda a "reemplazar" las macros es la capacidad de definir sus propios operadores infijos y personalizar su precedencia y asociatividad. Un poco complicado, ¡pero un sistema interesante!

J Cooper
fuente