¿Lenguajes de programación funcionales más puros? [cerrado]

20

Estoy interesado en aprender mejor la programación funcional. Para hacerlo, parece obvio que debería obligarme a usar el lenguaje de programación funcional más puro posible. Por lo tanto, estoy pidiendo, más o menos, un ordenamiento de lenguajes de programación funcionales de acuerdo con su pureza.

Me parece que sería más práctico aprender Lisp o Clojure (o Scheme, o Scala, etc.), pero por lo que he escuchado recientemente, Haskell sería muy difícil de superar al enseñar principios de programación funcional a alguien. Todavía no estoy seguro de esto, así que te pregunto: ¿ cuál es el lenguaje de programación funcional más puro? Un pedido sería genial si varios compiten por el magnífico título del lenguaje de programación funcional más puro.

Joanis
fuente
2
Aprendí Miranda en la universidad, así que soy parcial, pero sugeriría a Haskel a cualquiera que quiera sumergirse en un lenguaje funcional sin las distracciones de la impureza . * 8 ')
Mark Booth
2
Una vez que haya terminado de aprender programación funcional, también debe aprender programación estáticamente escrita con un sistema de tipo expresivo. En la categoría combinada (tanto funcional como tipificada), sugiero: Coq> Haskell> OCaml> Scala> otros. Hay algunas alternativas menos populares que encajan entre Coq y Haskell (como Epigram y Agda). Haskell echa de menos el sistema de módulos expresivos de OCaml.
lukstafi

Respuestas:

28

No hay escala para evaluar el grado de pureza de los lenguajes funcionales. Si el lenguaje permite efectos secundarios es impuro, de lo contrario es puro. Según esta definición, Haskell, Mercury, Clean, etc. son lenguajes funcionales puros; mientras que Scala, Clojure, F #, OCaml, etc.son impuras.

EDITAR: Tal vez debería haber redactado esto como "si el lenguaje no permite efectos secundarios sin que el sistema de tipos lo sepa , es puro. De lo contrario, es impuro".

missingfaktor
fuente
66
No del todo: Haskell permite efectos secundarios ( IOmónada). Es solo que el código que causa los efectos secundarios está claramente marcado como tal. No creo que sea útil hablar de lenguajes puros / impuros (¡Haskell te permite programar de manera imperativa! /impuro.
Frank Shearar
8
@Frank: Sí, Haskell permite efectos secundarios, pero hace más que solo marcar el código que causa los efectos secundarios. También mantiene la transparencia referencial incluso cuando se usa código que causa efectos secundarios y eso es lo que hace puro, al menos según la definición de pureza de muchas personas. Por supuesto, eso no coincide con la definición que falta de Faktor de "no se permiten efectos secundarios".
sepp2k
44
@ Frank Shearar, pero es útil hablar de esa manera porque la IOmónada es pura. Es la biblioteca de tiempo de ejecución que no es, no su código, ya que su mainfunción es básicamente un gran transformador de estado. (Algo así como main :: World -> Worlddetrás de escena)
alternativa el
1
Mi lenguaje también tiene transparencia referencial. Simplemente escribe program = "some C code", y luego el entorno de ejecución trata con el código C. :-)
Daniel Lubarov
3
Como imprimir en la pantalla es un efecto secundario, los programas funcionales verdaderamente puros son bastante aburridos.
15

Dado que el objetivo es aprender, y no escribir programas per se, no puede ser más puro que el cálculo Lambda .

El cálculo Lambda existía antes de que se inventaran las computadoras. Se necesitaron varios lógicos expertos trabajando en ello para descubrir cómo hacer la resta (por un tiempo se teorizó que solo era posible sumar y multiplicar).

Aprender cómo los booleanos y los números ifpueden inventarse a partir de aparentemente nada no pondrá más gasolina en su tanque, pero lo hará mucho más grande.

Macneil
fuente
De acuerdo, probablemente no haya nada más puro que eso, pero estamos cruzando demasiado la barrera matemática. Todavía quiero practicar programación y aprender un nuevo lenguaje mientras absorbo principios funcionales. Sin embargo, estoy de acuerdo en que podría ser interesante estudiar el cálculo lambda solo para entender mejor los fundamentos del paradigma funcional (y tener un tanque más grande).
Joanis
2
Escribir una prueba es escribir un programa y escribir un programa es escribir una prueba. Cuando aprenda sobre el isomorfismo de Curry-Howard, se dará cuenta de que pasó esa barrera matemática hace diez mil líneas.
Macneil
1
No hay una representación simple de -1.
Daniel Lubarov
2
@Mason Wheeler: el cálculo λ solo no tiene números, o realmente ningún tipo de datos además de las abstracciones lambda. A menos que se especifique lo contrario, cualquiera que hable de números en cálculo λ probablemente signifique la codificación de Church para números naturales , donde un número n está representado por la composición de la función n-fold. Dicho esto, estoy dudosa era que una lucha mucho para averiguar la resta una vez que alguien intentó realidad; Lo resolví solo en una tarde dada solo la definición de suma y el conocimiento de que la resta era posible.
CA McCann
1
Restar usando números de la Iglesia es fácil una vez que hayas descomposición por casos. Desarrollar todo el cálculo (derivado) para admitir números negativos, bastante más trabajo.
Donal Fellows
6

Los lenguajes impuros no difieren en principio de los lenguajes imperativos más familiares, especialmente ahora que se han copiado muchos trucos funcionales. Lo que es diferente es el estilo: cómo se resuelven los problemas.

Ya sea que cuente a Haskell como puro o que considere a la mónada IO como impureza, el estilo de Haskell es una forma extrema de este estilo y vale la pena aprenderlo.

La mónada Haskell IO se deriva de la teoría matemática de (por supuesto) mónadas. Sin embargo, para los programadores imperativos, creo que una forma inversa de llegar a las mónadas tiene más sentido.

Fase uno: un lenguaje funcional puro puede devolver fácilmente un valor de cadena grande como resultado. Esta gran cadena puede ser el código fuente de un programa imperativo, derivado de una manera puramente funcional de algunos parámetros que especifican requisitos. A continuación, puede crear un compilador de "nivel superior" que ejecute su generador de código, luego alimenta automáticamente ese código generado en el compilador de lenguaje imperativo.

Fase dos: en lugar de generar código fuente textual, genera un árbol de sintaxis abstracta fuertemente tipado. Su compilador de lenguaje imperativo se absorbe en su compilador de "nivel superior" y acepta el AST directamente como código fuente. Esto está mucho más cerca de lo que hace Haskell.

Sin embargo, esto todavía es incómodo. Por ejemplo, tiene dos tipos distintos de funciones: las evaluadas durante la fase de generación de código y las ejecutadas cuando se ejecuta el programa generado. Es un poco como la distinción entre funciones y plantillas en C ++.

Entonces, para la fase 3, haga que los dos sean iguales: la misma función con la misma sintaxis puede evaluarse parcialmente durante la "generación de código", o evaluarse completamente, o no evaluarse en absoluto. Además, descarte todos los nodos AST de construcción en bucle a favor de la recursividad. De hecho, descarte la idea de los nodos AST como un tipo especial de datos por completo: no tenga nodos AST de "valor literal", solo tenga valores, etc.

Esto es más o menos lo que hace la mónada IO: el operador de enlace es una forma de componer "acciones" para formar programas. No es nada especial, solo una función. Se pueden evaluar muchas expresiones y funciones durante la "generación de código", pero las que dependen de los efectos secundarios de E / S deben tener una evaluación retrasada hasta el tiempo de ejecución, no por ninguna regla especial, sino como consecuencia natural de las dependencias de datos en expresiones

Las mónadas en general son solo generalizaciones: tienen la misma interfaz, pero implementan las operaciones abstractas de manera diferente, por lo que en lugar de evaluar una descripción del código imperativo, evalúan otra cosa. Tener la misma interfaz significa que hay algunas cosas que puede hacer a las mónadas sin importar qué mónada, lo que resulta útil.

Esta descripción sin duda hará explotar las cabezas de los puristas, pero para mí explica algunas de las razones reales por las que Haskell es interesante. Borra el límite entre la programación y la metaprogramación, y utiliza las herramientas de programación funcional para reinventar la programación imperativa sin necesidad de una sintaxis especial.

Una crítica que tengo de las plantillas de C ++ es que son una especie de sublenguaje funcional puro roto en un lenguaje imperativo: para evaluar la misma función básica en tiempo de compilación en lugar de tiempo de ejecución, debe volver a implementarla usando un estilo completamente diferente de codificación En Haskell, aunque la impureza debe etiquetarse como tal en su tipo, la misma función exacta puede evaluarse tanto en un sentido de metaprogramación como en un sentido de no metaprogramación en tiempo de ejecución en el mismo programa: no hay una línea dura entre programación y metaprogramación.

Dicho esto, hay algunas cosas de metaprogramación que Haskell estándar no puede hacer, básicamente porque los tipos (y tal vez algunas otras cosas) no son valores de primera clase. Sin embargo, hay variantes de idioma que intentan abordar esto.

Muchas de las cosas que he dicho sobre Haskell pueden aplicarse en lenguajes funcionales impuros, y en ocasiones incluso en lenguajes imperativos. Haskell es diferente porque no tienes más remedio que adoptar este enfoque, básicamente te obliga a aprender este estilo de trabajo. Puede "escribir C en ML", pero no puede "escribir C en Haskell", al menos no sin saber lo que sucede debajo del capó.

Steve314
fuente
¡Gracias! Me preguntaba si el carácter genérico de las plantillas de C ++ podría haberse unificado en las funciones mismas. Parece que Haskell hace esto, pero la parte importante es si esto se puede implementar en un lenguaje compilado , de modo que el mismo motor que crea funciones genéricas a partir de plantillas durante el tiempo de compilación, también pueda evaluarlas durante el tiempo de ejecución ...
Milind R
5

Personalmente clasifico los idiomas en tres niveles de pureza funcional:

  • Lenguajes funcionales puros , es decir, aquellos que tratan todo su programa como una función pura y manejan la mutabilidad únicamente a través de la interacción con el tiempo de ejecución. Haskell es probablemente el ejemplo canónico.

  • Lenguajes funcionales impuros , es decir, aquellos que enfatizan un estilo funcional pero permiten efectos secundarios. Clojure está claramente en esta categoría (permite la mutación de forma controlada como parte de su marco STM), también OCaml o F #

  • Lenguajes de paradigmas múltiples : estos no son lenguajes funcionales en primer lugar, pero pueden admitir un estilo funcional mediante el uso de funciones de primera clase, etc. Scala es un buen ejemplo aquí, también pondría Common Lisp en esta categoría e incluso podría incluir idiomas como JavaScript .

En su situación, sugeriría aprender primero Haskell, luego Clojure. ¡Esto fue lo que hice y funcionó muy bien para mí! Haskell es hermoso y te enseña los principios funcionales más puros, Clojure es mucho más pragmático y te ayuda a hacer mucho mientras sigues siendo muy funcional de corazón.

Realmente no cuento la tercera categoría como lenguajes funcionales (aunque después de aprender Haskell y Clojure, ¡a menudo me encuentro aprovechando las técnicas funcionales cuando las uso!)

mikera
fuente
Dentro de limitaciones muy estrictas , incluso C tiene capacidades funcionales. (La principal limitación es la síntesis de las funciones de tiempo de ejecución, que es verdaderamente terrible en C, tanto es así que la mayoría de la gente dice que no se puede hacer.)
Donal Fellows
2
@Donal Fellows: en el límite en que la tontería llega al infinito, cada lenguaje completo de Turing es funcional: uno solo necesita implementar un intérprete de Lisp en el idioma y luego usarlo. :]
CA McCann
Los paradigmas no tienen sentido si considera que todo está completo y, por lo tanto, puede emular todo lo demás. Cuando piensas en paradigmas, debes concentrarte en lo que el lenguaje hace idiomático y en lo que desalienta / previene. En mi opinión, para contar como un lenguaje funcional, la mutación y los efectos secundarios deben ser unidiomáticos (para FP impuro) o prohibidos (para FP puro). Eso significa que C no es funcional. Tampoco lo son los lenguajes de paradigmas múltiples como Scala.
mikera
@mikera: Encuentro su respuesta muy interesante: si Scala no es funcional, sino que solo tiene varios paradigmas, ¿cómo juzga las características de programación funcional en C ++ 11, C # o incluso Java (la introducción planificada de lambdas)?
Giorgio
@Giorgio: Creo que las características funcionales en todos los idiomas que menciona son probablemente una buena idea, simplemente no los hacen "lenguajes funcionales". O para mirarlo desde la dirección opuesta, el hecho de que puedas hacer IO monádico de estilo imperativo no convierte a Haskell en un lenguaje imperativo :-). Los lenguajes de paradigmas múltiples son básicamente el resultado cuando se combinan características de muchos paradigmas diferentes y no se antepone ningún estilo en particular. En mi humilde opinión, C ++, C # y Java se están moviendo hacia un paradigma múltiple, pero aún no están allí, ya que la OOP basada en clases sigue siendo dominante.
mikera
3

Si un lenguaje funcional puro es tal, que solo tiene funciones puras (rutinas que no tienen efectos secundarios), entonces es un poco inútil, porque no puede leer la entrada o escribir la salida;)

Debido a que esto es realmente para aprender, creo que el aislamiento no es necesariamente el camino a seguir. La programación funcional es un paradigma. Es importante comprender qué paradigmas son adecuados para qué problemas y, lo que es más importante, cómo se pueden combinar mejor.

Lo voy a decir ahora: las modas de programación son estúpidas y contraproducentes. Lo único que importa es que su programa es corto, fácil de escribir, fácil de mantener y funciona correctamente. Cómo lograr esto no tiene nada que ver con las modas de programación. - Richard Jones

Aparte de eso, si buscas "pureza", quizás quieras echarle un vistazo a Pure . Sin embargo, tenga en cuenta que la extrema facilidad de llamar a las rutinas C lo hace funcionalmente impuro (pero también muy poderoso).

back2dos
fuente
3

No es una respuesta completamente seria, pero Unlambda tiene que ser un contendiente. No se puede obtener más "puramente funcional" que los combinadores de SKI.

Stephen C
fuente
44
¡Locura! ¡Herejía! ¿Qué tipo de lenguaje puro tiene absurdos como los combinadores con efectos secundarios? No no. Lo que realmente quiere es aquí Lazy K .
CA McCann
2

Erlang, Haskell, Scheme, Scala, Clojure y F #

Probablemente, esta pregunta sea mejor para ayudarlo en su búsqueda también .

programador de polvo
fuente
Gracias, pregunta interesante de hecho. He comenzado con PLT-Scheme / Racket, pero todavía no he visto SICP ... Real World Haskell también me parece bastante interesante.
Joanis
Scheme fomenta un estilo funcional, pero tiene set!(entre otras cosas) ...
Daniel Lubarov
Sugiero OCaml, porque es mi favorito. Y tiene un sistema de módulos, que Haskell y F # fallan.
lukstafi
@lukstafi quizás haskell ha cambiado en los últimos 3 años (sé que ha crecido como loco), pero definitivamente hay módulos en haskell.
sara
@kai Por sistema de módulos, me refería a módulos parametrizados por otros módulos (un "cálculo lambda" de módulos).
lukstafi