Después de unos meses de aprender y jugar con Lisp, tanto CL como un poco de Clojure, todavía no veo una razón convincente para escribir nada en lugar de C #.
Realmente me gustaría algunas razones convincentes, o para que alguien señale que me estoy perdiendo algo realmente grande .
Los puntos fuertes de Lisp (según mi investigación):
- Notación compacta y expresiva: más que C #, sí ... pero parece que también puedo expresar esas ideas en C #.
- Soporte implícito para programación funcional - C # con métodos de extensión LINQ:
- mapcar = .Seleccionar (lambda)
- mapcan = .Select (lambda) .Agregre ((a, b) => a.Union (b))
- coche / primero = .Primero ()
- cdr / rest = .Skip (1) .... etc.
- Compatibilidad con funciones Lambda y de orden superior: C # tiene esto y la sintaxis es posiblemente más simple:
- "(lambda (x) (cuerpo))" versus "x => (cuerpo)"
- "# (" con "%", "% 1", "% 2" es bueno en Clojure
- Despacho de método separado de los objetos: C # tiene esto a través de métodos de extensión
- Despacho multimetodo: C # no tiene esto de forma nativa, pero podría implementarlo como una llamada de función en unas pocas horas
- El código es datos (y macros): tal vez no he "obtenido" macros, pero no he visto un solo ejemplo en el que la idea de una macro no se pueda implementar como una función; no cambia el "idioma", pero no estoy seguro de que sea una fortaleza
- DSL: solo puede hacerlo a través de la composición de funciones ... pero funciona
- Programación "exploratoria" sin tipo: para estructuras / clases, las autopropertías de C # y el "objeto" funcionan bastante bien, y puede escalar fácilmente a una escritura más fuerte a medida que avanza
- Se ejecuta en hardware que no es de Windows. Fuera de la universidad, solo he conocido a una persona que no ejecuta Windows en casa, o al menos una VM de Windows en * nix / Mac. (Por otra parte, tal vez esto es más importante de lo que pensaba y me acaban de lavar el cerebro ...)
- El REPL para el diseño de abajo hacia arriba: Ok, admito que esto es realmente muy agradable, y lo extraño en C #.
Cosas que me faltan en Lisp (debido a una combinación de C #, .NET, Visual Studio, Resharper):
- Espacios de nombres. Incluso con métodos estáticos, me gusta vincularlos a una "clase" para clasificar su contexto (Clojure parece tener esto, CL no parece tener).
- Gran compilación y soporte en tiempo de diseño
- el sistema de tipos me permite determinar la "corrección" de las estructuras de datos que paso
- todo lo que está mal escrito está subrayado en tiempo real; No tengo que esperar hasta el tiempo de ejecución para saber
- las mejoras de código (como el uso de un enfoque FP en lugar de uno imperativo) se sugieren automáticamente
- Herramientas de desarrollo de GUI: WinForms y WPF (sé que Clojure tiene acceso a las bibliotecas de GUI de Java, pero son completamente ajenas a mí).
- Herramientas de depuración de la interfaz gráfica de usuario: puntos de interrupción, paso, paso, inspectores de valor (texto, xml, personalizado), relojes, depuración por subproceso, puntos de interrupción condicionales, ventana de pila de llamadas con la capacidad de saltar al código en cualquier nivel en la pila
- (Para ser justos, mi período con Emacs + Slime parecía proporcionar algo de esto, pero soy parcial con el enfoque de VS GUI)
Realmente me gusta el bombo que rodea a Lisp y le di una oportunidad.
Pero, ¿hay algo que pueda hacer en Lisp que no pueda hacer tan bien en C #? Puede ser un poco más detallado en C #, pero también tengo autocompletar.
¿Qué me estoy perdiendo? ¿Por qué debería usar Clojure / CL?
AND
oOR
como una función. Podría hacerse (dadoLAMBDA
, que también es una macro), pero no veo una forma obvia de hacerlo que no apestara totalmente.:
(o::
para símbolos no exportados) para nombrar símbolos en paquetes, por ejemplo, su ejemplo aquí usaríahttp:session
ychat:session
.Respuestas:
Las macros le permiten escribir compiladores sin dejar la comodidad de su idioma. Las DSL en lenguajes como C # generalmente equivalen a un intérprete de tiempo de ejecución. Dependiendo de su dominio, eso podría ser problemático por razones de rendimiento. La velocidad sí importa , de lo contrario estaría codificando en un lenguaje interpretado y no en C #.
Además, las macros también le permiten considerar factores humanos: ¿cuál es la sintaxis más fácil de usar para un DSL en particular? Con C # no hay mucho que pueda hacer sobre la sintaxis.
Entonces Lisp le permite participar en el diseño del lenguaje sin sacrificar un camino hacia la eficiencia . Y aunque estos problemas pueden no ser importantes para usted , son extremadamente importantes ya que subyacen en los cimientos de todos los lenguajes de programación útiles orientados a la producción. En C #, este elemento fundamental está expuesto a usted en una forma muy limitada en el mejor de los casos.
La importancia de las macros no se pierde en otros idiomas: me viene a la mente la plantilla Haskell, camlp4. Pero, una vez más, es una cuestión de usabilidad : las macros Lisp hasta la fecha son probablemente la implementación más fácil de usar pero poderosa de la transformación en tiempo de compilación.
Entonces, en resumen, lo que la gente generalmente hace con lenguajes como C # es construir DSIL (lenguajes interpretados específicos del dominio). Lisp te da la oportunidad de construir algo mucho más radical, DSP (Paradigmas específicos de dominio). Racket (anteriormente PLT-Scheme) es particularmente inspirador a este respecto: tienen un lenguaje perezoso (Lazy Racket), uno escrito (Typed Racket), Prolog y Datalog, todos integrados idiomáticamente a través de macros. Todos estos paradigmas proporcionan soluciones poderosas para grandes clases de problemas, problemas que no se resuelven mejor mediante programación imperativa o incluso paradigmas de FP.
fuente
Afortunadamente, casi todo lo que originalmente estaba disponible solo en Lisps ha migrado a muchos otros idiomas modernos. La mayor excepción es la filosofía "código es datos" y las macros.
Sí, las macros son difíciles de asimilar. La metaprogramación en general es difícil de asimilar. Si vio alguna macro que podría implementarse como una función, entonces el programador lo estaba haciendo mal. Las macros solo deben usarse cuando una función no funciona.
El ejemplo de "hola mundo" de una macro es escribir "if" en términos de cond. Si está realmente interesado en aprender el poder de las macros, intente definir un "my-if" en clojure, utilizando funciones y observe cómo se rompe. No quiero ser misterioso, nunca he visto a nadie comenzar a entender las macros solo por explicación, pero esta presentación de diapositivas es una muy buena introducción: http://www.slideshare.net/pcalcado/lisp-macros-in -20 minutos-presentando-clojure-presentación
He tenido pequeños proyectos en los que he guardado literalmente cientos / miles de líneas de código usando macros (en comparación con lo que hubiera tomado en Java). Gran parte de Clojure se implementa en Clojure, que solo es posible debido al sistema macro.
fuente
(defun my-if (test then &optional else) (cond (test (funcall then)) ('otherwise (funcall else))))
No soy un experto en Common Lisp, pero sé que tanto C # como Clojure son bastante buenos, así que espero que esta sea una perspectiva útil.
Como resultado de la sintaxis increíblemente simple, la filosofía de "código es datos" de Lisp es mucho más poderosa que cualquier cosa que pueda hacer con la composición / plantillas / genéricos de funciones, etc. Es más que solo DSL, es generación de código y manipulación, en tiempo de compilación , como característica fundamental del lenguaje. Si intentas obtener este tipo de capacidades en un lenguaje no homoicónico como C # o Java, eventualmente terminarás reinventando a Lisp. De ahí la famosa Décima Regla de Greenspuns: "Cualquier programa C o Fortran suficientemente complicado contiene una implementación ad hoc, especificada informalmente, llena de errores y lenta de la mitad de Common Lisp". Si alguna vez te has encontrado inventando un lenguaje de plantillas / control de flujo completo dentro de tu aplicación, entonces probablemente sabes lo que quiero decir ...
La concurrencia es una fortaleza única de Clojure (incluso en relación con otros Lisps), pero si cree que el futuro de la programación significará tener que escribir aplicaciones que escalen a máquinas altamente multinúcleo, entonces realmente debería considerar esto: es bastante innovador Clojure STM (Software Transactional Memory) funciona porque Clojure adopta construcciones de inmutabilidad y concurrencia sin bloqueo basadas en una novedosa separación de identidad y estado (ver el excelente video de Rich Hickey sobre identidad y estado ). Sería muy doloroso injertar este tipo de capacidad de concurrencia en un entorno de lenguaje / tiempo de ejecución donde una proporción significativa de las API funciona en objetos mutables.
Programación funcional : Lisps enfatiza la programación con funciones de orden superior. Tiene razón en que puede emularse en objetos OO con objetos de cierre / función, etc. pero la diferencia es que todo el lenguaje y la biblioteca estándar están diseñados para ayudarlo a escribir código de esta manera. Por ejemplo, las secuencias de Clojure son perezosas , por lo que pueden ser infinitamente largas a diferencia de sus ArrayLists o HashMaps en C # / Java. Esto hace que algunos algoritmos sean mucho más fáciles de expresar, por ejemplo, lo siguiente implementa una lista infinita de números de Fibonacci:
(def fibs (lazy-cat '(0 1) (map + fibs (drop 1 fibs))))
Escritura dinámica : los Lisps tienden a estar en el extremo "dinámico" del espectro del lenguaje de programación funcional (a diferencia de los idiomas como Haskell con una concepción muy fuerte y hermosa de los tipos estáticos). Esto tiene ventajas y desventajas, pero diría que los lenguajes dinámicos tienden a favorecer la productividad de la programación debido a una mayor flexibilidad. Esta escritura dinámica tiene un costo de rendimiento de tiempo de ejecución menor, pero si eso le molesta, siempre puede agregar sugerencias de tipo a su código para obtener una escritura estática. Básicamente: obtienes comodidad de forma predeterminada y rendimiento si estás dispuesto a hacer un poco de trabajo extra para conseguirlo.
Desarrollo interactivo : en realidad creo que puede obtener algún tipo de REPL para C # 4.0, pero en Lisp es una práctica estándar en lugar de una novedad. No tiene que hacer un ciclo de compilación / compilación / prueba en absoluto: escribe su código directamente en el entorno de ejecución y solo más tarde lo copia en sus archivos de origen. Te enseña una forma muy diferente de codificar, donde ves resultados inmediatamente y refinas tu solución de forma iterativa.
Algunos comentarios rápidos sobre las cosas que ves como "faltantes":
Personalmente, encuentro las ventajas de Clojure / Lisp bastante convincentes, y no planeo volver a C # / Java (¡más allá de lo que necesito para tomar prestadas todas las bibliotecas útiles!).
Sin embargo, me llevó unos meses entenderlo todo. Si está realmente interesado en aprender Lisp / Clojure como una experiencia de aprendizaje esclarecedora, no hay sustituto para sumergirse y obligarse a implementar algunas cosas .....
fuente
C # tiene muchas características, pero la mezcla se siente diferente. Que algún idioma tenga una característica aún no significa que sea fácil de usar, paradigmático y esté bien integrado con el resto del lenguaje.
Common Lisp tiene esta mezcla:
Ahora, otros lenguajes, como C #, también tienen eso. Pero a menudo no con el mismo énfasis.
C # tiene
No ayuda que C # tenga, por ejemplo, características de manipulación de código, si no se usan ampliamente como en Lisp. En Lisp no encontrará muchos programas que no hagan un uso intensivo de las macros en todo tipo de formas simples y complejas. El uso de la manipulación de código es realmente una gran característica en Lisp. Emular algunos usos es posible en muchos idiomas, pero no lo convierte en una característica central como en Lisp. Vea libros como 'On Lisp' o 'Practical Common Lisp' para ver ejemplos.
Lo mismo para el desarrollo interactivo. Si desarrolla un sistema CAD grande, la mayor parte del desarrollo será interactivo desde el editor y REPL, directamente en la aplicación CAD en ejecución. Puede emular mucho de eso en C # u otros lenguajes, a menudo implementando un lenguaje de extensión. Para Lisp sería estúpido reiniciar la aplicación CAD en desarrollo para agregar cambios. El sistema CAD también contendría un Lisp mejorado que habría agregado características de lenguaje (en forma de macros y descripciones simbólicas) para describir objetos CAD y sus relaciones (parte de, contenido, ...). Se pueden hacer cosas similares en C #, pero en Lisp este es el estilo de desarrollo predeterminado.
Si desarrolla un software complejo utilizando un proceso de desarrollo interactivo o si su software tiene una parte simbólica sustancial (meta programación, otros paradigmas, álgebra informática, composición musical, planificación, ...), entonces Lisp puede ser para usted.
Si trabaja en el entorno de Windows (no lo hago), entonces C # tiene una ventaja, ya que es un lenguaje de desarrollo principal de Microsoft. Microsoft no admite ninguna forma de Lisp.
Usted escribe:
Common Lisp no adjunta espacios de nombres a las clases. Los espacios de nombres son proporcionados por paquetes de símbolos y son independientes de las clases. Necesita reorientar su enfoque a la programación orientada a objetos si usa CLOS.
Usa el compilador Lisp. Le informa acerca de variables desconocidas, puede tener recomendaciones de estilo (dependiendo del compilador utilizado), le da pistas de eficiencia, ... El compilador está a una pulsación de tecla.
Los Lisps comerciales como LispWorks y Allegro CL ofrecen cosas similares en Windows.
Los Lisps comerciales como LispWorks y Allegro CL ofrecen todo eso bajo Windows.
fuente
Rainiero Joswig lo dijo mejor.
Para mí, el poder de Lisp no se puede entender simplemente mirando una lista de características de Lisp. Para Lisp, y Common Lisp en particular, el todo es mayor que la suma de las partes. No es solo REPL más expresiones s más macros más despacho de métodos múltiples más cierres más paquetes más CLOS más reinicios más X, Y y Z. Es todo el shebang todo ingeniosamente integrado. Es la experiencia cotidiana que es muy diferente a la experiencia cotidiana de otros lenguajes de programación.
Lisp se ve diferente, se usa de manera diferente, uno se acerca a la programación de manera diferente cuando lo usa. Es elegante, completo, grande y sin costuras. No está exento de imperfecciones y pecadillos. Ciertamente, hay comparaciones con otros idiomas que podrían hacerse donde podría no salir adelante. Pero de ninguna manera Lisp es solo lenguaje X más REPL y macros, o lenguaje Y más despacho y reinicio de métodos múltiples.
fuente
Típicamente, una macro debe usarse para algo que no se puede expresar como una llamada a función. No sé si hay un condicional de tipo switch en C # (similar a lo que existe en C, como switch (form) {case ...}), pero supongamos que sí y tiene casi las mismas restricciones ( que requiere un tipo integral).
Ahora, supongamos que le gustaría algo que se parezca a eso, pero que se despache en cadenas. En lisp (bueno, específicamente en Common Lisp y probablemente en otros), eso es "solo" una macro de distancia y si restringe al usuario a tener cadenas constantes (probablemente no arduas) para comparar, puede calcular (tiempo de compilación) una secuencia mínima de pruebas para determinar qué caso coincide (o usar una tabla hash, almacenando cierres, tal vez).
Si bien es posible expresar eso como una función (cadena de comparación, una lista de casos y cierres para ejecutar), probablemente no se verá como "código normal" y es ahí donde las macros lisp tienen una victoria definitiva. Probablemente ha utilizado un buen número de macros o formas especiales taht son macro-como, como
defun
,defmacro
,setf
,cond
,loop
y muchos más.fuente
Este es el mejor artículo explicativo que he encontrado que saca al lector de la superficie de la defensa de Lisp para mostrar algunas de las implicaciones más profundas de los conceptos que se utilizan:
http://www.defmacro.org/ramblings/lisp.html
Recuerdo haber leído en otra parte una idea como esta: otros idiomas han adoptado varias características de Lisp; recolección de basura, tipeo dinámico, funciones anónimas, cierres para producir un mejor C ++ / C / Algol. Sin embargo, para obtener todo el poder de Lisp, necesita todas sus características esenciales, en ese momento tiene otro dialecto de Lisp, una familia distinta de idiomas que ha existido de una forma u otra durante más de 50 años.
fuente