Si podemos hacer programación funcional con Python, ¿necesitamos un lenguaje de programación funcional específico? [cerrado]

22

Usando generadores y lambda, podemos hacer programación funcional con Python. También puedes lograr lo mismo con Ruby.

Entonces la pregunta es: ¿por qué necesitamos lenguajes de programación funcionales específicos como Erlang, Haskell y Scheme? ¿Hay algo diferente que proporcionan estos lenguajes de programación funcionales específicos? ¿Por qué no podemos usar Python para la programación funcional?

Joshua Partogi
fuente
30
todo eso existía incluso antes de que se creara Python
Mahmoud Hossam
51
Un Ford Pinto es un auto. ¿Por qué necesitamos autos rápidos específicos como Ferrari?
Martin York
11
Usando clases y plantillas, podemos hacer cualquier cosa OO en C ++. ¿Por qué se crearon Java y Python? ¿Qué agregan?
9000
19
Todos los lenguajes de programación (sin algunos lenguajes de investigación puramente académicos) son equivalentes de Turing, por lo que lo que puede hacer en el lenguaje A puede hacerlo en cualquier otro idioma. Entonces, siguiendo este tren de pensamiento, solo necesitamos un lenguaje completo de Turing - digamos como sendmail.cf;) okmij.org/ftp/Computation/#sendmail-Turing
Maglob
17
Si conociera alguno de estos lenguajes, no diría que Python realiza bien la programación funcional. No lo hace. Lo hace lo suficientemente bien como para incorporar una parte de las cosas FP-ish, pero no mejor.

Respuestas:

20

Aprecio la pregunta, porque personalmente soy un gran admirador tanto de Python como del estilo funcional de programación. Tengo una larga experiencia en Python y comencé a aprender Haskell recientemente, así que aquí hay algunos puntos basados ​​en mis experiencias personales sobre las diferencias entre estos lenguajes, desde una perspectiva funcional.

Pureza

Incluso si no le importa la pureza de las funciones (es decir, no tener efectos secundarios) como un principio, tiene un efecto práctico sobre lo fácil que es leer el código y razonar al respecto. Incluso si mantiene la pureza en sus propias funciones de Python, existe una gran diferencia en hacer que el compilador haga cumplir la pureza y, sobre todo, en tener la biblioteca estándar construida en términos de pureza y estructuras de datos inmutables.

Actuación

Puede que le interese o no el rendimiento dependiendo del dominio de su aplicación, pero la escritura estática y la pureza garantizada le dan al compilador mucho más para trabajar, en comparación con Python y otros lenguajes dinámicos (aunque tengo que admitir que PyPy está haciendo un gran trabajo) incursiones, y por ejemplo, LuaJIT raya en lo milagroso).

Optimización de llamadas de cola

Relacionado con el rendimiento, pero ligeramente diferente. Incluso si no le importa demasiado el rendimiento en tiempo de ejecución, no tener una optimización de llamada de cola (especialmente para la recursividad de cola) limita las formas en que puede implementar algoritmos en Python sin alcanzar los límites de pila.

Sintaxis

Esta es la razón principal por la que comencé a mirar lenguajes funcionales "reales" en lugar de usar Python con un estilo funcional. Aunque creo que Python tiene una sintaxis muy expresiva en general, tiene algunos puntos débiles específicos de la codificación funcional. Por ejemplo:

  • La sintaxis para las funciones lambda es bastante detallada y limitada en lo que pueden contener
  • Sin azúcar sintáctico para la composición de la función, es decir f = g . hvs.f = lambda *arg: g(h(*arg))
  • Sin azúcar sintáctico para aplicación parcial, es decir f = map gvs.f = functools.partial(map, g)
  • Sin azúcar sintáctico para usar operadores infijos en funciones de orden superior, es decir sum = reduce (+) lstvs.sum = reduce(operator.add, lst)
  • No hay coincidencia de patrones ni protectores para los argumentos de la función, lo que facilita la expresión de las condiciones finales de recursión y algunos casos de borde con una sintaxis muy legible.
  • Los corchetes nunca son opcionales para las llamadas a funciones, y no hay un azúcar sintáctico para las llamadas anidadas. Supongo que esto es cuestión de gustos, pero especialmente en el código funcional, encuentro que es común encadenar llamadas de función y me resulta y = func1 $ func2 $ func3 xmás fácil de leer que y = func1(func2(func3(x))), una vez que esté familiarizado con esa notación.
shang
fuente
28

Estas son las diferencias más importantes:

Haskell

  • Evaluación perezosa
  • Compila al código de máquina
  • La escritura estática garantiza que las funciones sean puras
  • Inferencia de tipos

Haskell y Erlang

  • La coincidencia de patrones

Erlang

  • Modelo de concurrencia del actor, procesos livianos

Esquema

  • Macros

Todos los idiomas

  • cierres reales (Ruby tiene cierres, si se puede debatir sobre Python, vea los comentarios)
  • Una biblioteca estándar adecuada para un estilo de programación funcional (colecciones inmutables, mapas, filtros, pliegues, etc.)
  • recursividad de cola (esto también se puede encontrar en algunos lenguajes no funcionales)

Además, debe echar un vistazo a los idiomas de la familia ML como SML, Ocaml y F # y Scala, que fusiona OO y la programación funcional de una manera nueva. Todos estos idiomas tienen características únicas e interesantes.

Kim
fuente
3
+1 buena publicación. Puede agregar inferencia de tipos en Haskell y procesos ligeros en Erlang.
Jonas
1
Python tiene mapa, filtro y plegado (reducir). Con respecto a los "cierres reales": si define un cierre real como un cierre que puede contener algo más que una sola expresión, Haskell tampoco tiene cierres reales (pero, por supuesto, Haskell tiene algunas cosas que no son expresiones ...) . Sin embargo, el punto sobre las estructuras de datos inmutables es bueno, entonces +1 para eso. También: recursividad de la cola (y generalmente recursividad menos costosa).
sepp2k
2
+1 para "la escritura estática garantiza que las funciones sean puras". Es genial tener un sistema de tipos que discrimine entre funciones puras y no puras. (Const doe snot count de C ++. :)
Macke
1
@btilly: No lo consideraría un cierre si no puede asignar a una variable que está dentro del alcance. De lo contrario, tendríamos que decir que Java también tiene cierres, ya que puedes usar el mismo truco allí.
Kim
3
En un cierre, puedo acceder a una variable de la misma manera que normalmente puedo. Esto es cierto para los cierres de Haskell y Ruby, pero no para los pobres sustitutos de Python o Java. Tal vez alguien más pueda informarnos sobre erlang, no lo sé tan bien.
Kim
19

Es difícil definir exactamente qué es un "lenguaje funcional": de los idiomas que enumeró, solo Haskell es puramente funcional (todos los demás adoptan algún tipo de enfoque híbrido). Sin embargo, hay ciertas características de lenguaje que son muy útiles para la programación funcional, y Ruby y Python no tienen suficientes para ser entornos muy buenos para FP. Aquí está mi lista de verificación personal, en orden de importancia:

  1. Funciones y cierres de primera clase (Ruby, Python y todos los demás que enumeró tienen esto).
  2. Optimización garantizada de llamadas de cola (Erlang, Haskell, Scala y Scheme tienen esto, pero no Python, Ruby o Clojure (todavía)).
  3. Soporte para la inmutabilidad en el lenguaje y las bibliotecas estándar (este es uno de los grandes que tienen todos los "lenguajes funcionales" que enumeró (excepto Scheme) pero Ruby y Python no lo tienen).
  4. Soporte a nivel de lenguaje para funciones referencialmente transparentes (o puras) (que yo sepa, solo Haskell tiene esto actualmente).

La necesidad de (1) debería ser obvia: las funciones de orden superior son extremadamente difíciles sin funciones de primera clase. Cuando la gente habla de que Ruby y Python son buenos lenguajes para FP, generalmente hablan de esto. Sin embargo, esta característica particular es necesaria pero no suficiente para hacer que un lenguaje sea bueno para FP.

(2) ha sido una necesidad tradicional para FP desde que se inventó el Scheme. Sin TCO, es imposible programar con una recursión profunda, que es una de las piedras angulares de FP, porque se producen desbordamientos de pila. El único lenguaje "funcional" (por definición popular) que no tiene esto es Clojure (debido a las limitaciones de la JVM), pero Clojure tiene una variedad de hacks para simular el TCO. (Para su información, Ruby TCO es específico de la implementación , pero Python específicamente no lo admite ). La razón por la que debe garantizarse el TCO es que si es específica de la implementación, las funciones recursivas profundas se romperán con algunas implementaciones, por lo que no puede realmente úsalos en absoluto.

(3) es otra gran cosa que los lenguajes funcionales modernos (especialmente Haskell, Erlang, Clojure y Scala) tienen que Ruby y Python no tienen. Sin entrar en demasiados detalles, la inmutabilidad garantizada elimina clases enteras de errores, especialmente en situaciones concurrentes, y permite cosas claras como estructuras de datos persistentes . Es muy difícil aprovechar estos beneficios sin soporte de nivel de idioma.

(4) es, para mí, lo más interesante de los lenguajes puramente funcionales (a diferencia de los lenguajes híbridos). Considere la siguiente función de Ruby extremadamente simple:

def add(a, b)
  a + b
end

Esto parece una función pura, pero debido a la sobrecarga del operador, podría mutar cualquiera de los parámetros o causar efectos secundarios como imprimir en la consola. Es poco probable que alguien sobrecargue al +operador para tener un efecto secundario, pero el lenguaje no ofrece garantías. (Lo mismo se aplica a Python, aunque tal vez no con este ejemplo específico).

En un lenguaje puramente funcional, por otro lado, existen garantías a nivel de lenguaje de que las funciones son referencialmente transparentes. Esto tiene numerosas ventajas: las funciones puras se pueden memorizar fácilmente; se pueden probar fácilmente sin depender de ningún tipo de estado global; y los valores dentro de la función pueden evaluarse perezosamente o en paralelo sin preocuparse por los problemas de concurrencia. Haskell aprovecha al máximo esto, pero no sé lo suficiente sobre otros lenguajes funcionales para saber si lo hacen.

Dicho todo esto, es posible utilizar técnicas de FP en casi cualquier lenguaje (incluso Java). Por ejemplo, MapReduce de Google está inspirado en ideas funcionales, pero que yo sepa, no usan ningún lenguaje "funcional" para sus grandes proyectos (creo que usan principalmente C ++, Java y Python).

shosti
fuente
2
+1 por la explicación detallada, incluso yo, como un extraño de FP, lo entendí. ¡Gracias! :-)
Péter Török
La respuesta más valiosa a esta pregunta hasta ahora. Fundado en hechos y mucha información. Gran trabajo.
wirrbel
Scala tiene una recursión de cola y, al igual que con Scheme, se hace automáticamente si una llamada recursiva está en posición de cola (a diferencia de Clojure, donde debe solicitarse explícitamente). Incluso hay una anotación para que pueda comprobar que el compilador va a generar código recursivo cola. Lo que no tiene es el TCO más generalizado de Scheme. Supongo que lo sabes, pero dado que entraste en tantos detalles sobre la mayoría de las otras cosas, eso pareció un extraño despido / omisión.
itsbruce
@itsbruce Esta publicación es bastante antigua, IIRC Scala no la tenía en ese momento (o puede que me haya equivocado;). Actualizado.
shosti
No he estado usando Scala desde el principio, pero tuvo una recurrencia de cola en 2008, cuando me estaba interesando ;-) Remito a las personas que preguntan sobre este tema a esta pregunta SO en particular porque tiene algunas buenas respuestas y solo acabo Noté esa rareza, así que comenté por lo completo.
itsbruce
11

Los idiomas que mencionas son muy diferentes.

Mientras Python y Ruby son lenguajes de escritura dinámica, Haskell está estáticamente escrita. Erlang es un idioma concurrente y utiliza el modelo de actor y es muy diferente de todos los demás idiomas que menciona.

Python y Ruby tienen muchas construcciones imperativas, mientras que en un lenguaje funcional más puro como Haskell, todo devuelve algo o, en otras palabras, todo es una función.

Jonas
fuente
@kRON: Bueno, el sistema de tipos es una propiedad importante de un lenguaje, y él preguntó "¿Hay algo diferente que estos lenguajes de programación funcionales específicos proporcionan?". Claro, puede usar el modelo de actor con otros idiomas, pero Erlang lo tiene incorporado en el idioma. Erlang utiliza procesos ligeros y tiene construcciones de lenguaje incorporadas para programación distribuida, de ahí un lenguaje "concurrente".
Jonas
8

Tarde a la fiesta como siempre, pero de todas maneras voy a decir cosas.

Un lenguaje de programación funcional no es un lenguaje que permita la programación funcional. Si tuviéramos que seguir esa definición, casi cualquier lenguaje en cualquier lugar es un lenguaje de programación funcional. (Lo mismo se aplica a OOP, por cierto. Puede escribir en un estilo OOP en C si lo desea. Por lo tanto, de acuerdo con su lógica, C es un lenguaje OOP).

Lo que hace que un lenguaje de programación funcional no sea lo que le permite programar, es lo que le permite programar fácilmente . Esa es la clave.

Por lo tanto, Python tiene lambdas (que son asuntos increíblemente anémicos) y le brinda un par de funciones de biblioteca que verá en bibliotecas funcionales, así como "mapa" y "pliegue". Sin embargo, esto no es suficiente para convertirlo en un lenguaje de programación funcional, porque es difícil o imposible programar consistentemente en un estilo funcional adecuado (¡y el lenguaje ciertamente no aplica este estilo!). En esencia, Python es un lenguaje imperativo relacionado con las operaciones de manipulación de estado y estado y esto simplemente está en desacuerdo con la expresión y la semántica de evaluación de expresiones de un lenguaje funcional.

Entonces, ¿por qué tenemos lenguajes de programación funcionales cuando Python (o Ruby (o inserte el lenguaje de su elección)) puede "hacer programación funcional"? Porque Python, et al no pueden hacer una programación funcional adecuada. Es por eso.

SOLO MI OPINIÓN correcta
fuente
6

Usted puede hacer la programación funcional en Java (véase, por ejemplo http://functionaljava.org/ ). También se puede hacer la programación orientada a objetos en C . Simplemente no es tan idiomático.

Entonces, de hecho, no necesitamos absolutamente Erlang, Haskell, Scheme o ningún lenguaje de programación específico, pero todos representan diferentes enfoques y diferentes compensaciones, lo que hace que algunas tareas sean más fáciles y más difíciles. Lo que debe usar depende de lo que quiera lograr.

Joonas Pulakka
fuente
4

Esta pregunta puede aplicarse a un número infinito de lenguajes y paradigmas.

  • Como todos usan C ++, ¿por qué necesitamos otros lenguajes de uso general?
  • Dado que Java es un excelente lenguaje OO, ¿por qué existen otros idiomas OO?
  • Dado que Perl es un lenguaje de script increíble, ¿por qué necesitamos Python?
  • Yatta, Yatta, Yatta

La mayoría, si no todos los idiomas existen por una razón específica. Existen porque alguien tenía una necesidad de que no se llenara el idioma actual, o que se llenara mal. (Por supuesto, esto no se aplica a todos los idiomas, pero creo que se aplica a la mayoría de los idiomas conocidos). Por ejemplo, python se desarrolló originalmente para interactuar con Amoeba OS [ 1 , 2 ] y Erlang se creó para ayudar en el desarrollo de aplicaciones de telefonía [ 3 ]. Entonces, una respuesta a la pregunta "¿Por qué necesitamos otro lenguaje funcional?" simplemente puede ser porque a [insert-name-of-someone-who-know-how-to-design-languages] no le gustó la forma en que Python lo hizo.

Eso resume bastante bien lo que creo que es la respuesta. Si bien puedes hacer cualquier cosa con Python que puedas hacer con un lenguaje funcional, ¿realmente quieres hacerlo? Todo lo que puedes hacer en C, puedes hacerlo en ensamblaje, pero ¿te gustaría hacerlo? Diferentes idiomas siempre serán mejores para hacer cosas diferentes, y así es como debe ser.

Cledoux
fuente
2

La programación funcional se trata tanto de un paradigma de diseño como de características específicas del lenguaje. O, dicho de otro modo, lambdas y una función de mapa no hacen un lenguaje de programación funcional. Python y Ruby tienen algunas características inspiradas en lenguajes de programación funcionales, pero aún así generalmente escribes código de una manera muy imperativa. (Es algo así como C: puede escribir código similar a OO en C, pero nadie considera seriamente que C sea un lenguaje OO).

Mire: la programación funcional no se trata solo de lambdas, o mapfunciones de orden superior. Se trata de diseño . Un programa escrito en un lenguaje de programación funcional "verdadero" resuelve problemas mediante la composición de funciones. Si bien los programas escritos en Ruby o Python pueden usar características similares a FP, generalmente no se leen como un conjunto de funciones compuestas.

mipadi
fuente
0

Cada lenguaje funcional que mencionó encaja bastante bien en cierto nicho y los patrones idiomáticos que cada uno fomenta los hace muy adecuados para ciertas tareas que serían imposibles de lograr en Python a menos que haya construido una gran biblioteca de módulos auxiliares. El más obvio de uno de esos nichos de excelencia es el modelo de concurrencia de Erlang. Los otros también tienen fortalezas similares.

davidk01
fuente
0

Todos los conceptos disponibles en lambda-calculus, LISP y Scheme están disponibles en Python, por lo que sí, puedes hacer programación funcional en él. Si es conveniente o no, es una cuestión de contexto y gusto.

Puede escribir fácilmente un intérprete LISP (u otro lenguaje funcional) en Python (Ruby, Scala) (¿qué significa eso?). Puede escribir un intérprete para Python utilizando funciones puramente funcionales, pero tomaría mucho trabajo. Incluso los lenguajes "funcionales" son multi-paradigma hoy en día.

Este viejo y hermoso libro tiene la mayoría (aunque no todas) las respuestas sobre cuál es la esencia de la programación funcional.

Apalala
fuente
@Arkaaito ¿Pero está de acuerdo en que todos los conceptos disponibles en cálculo lambda, LISP y Scheme están disponibles en Python?
Mark C
¿Estás seguro de que todos los conceptos de lisp están disponibles en Python? ¿Macros, código-es-datos?
SK-logic
@ SK-logic Las macros no están en cálculo lambda, pero sí, están disponibles en Python, aunque no con ese nombre. Python proporciona una eval()función, que es necesaria para que el código sea datos , pero va más allá: le permite modificar la mayor parte del entorno de tiempo de ejecución, como en LISP.
Apalala
@Apalala, los lenguajes dinámicos eval()son una metaprogramación en tiempo de ejecución. A veces es útil, pero es extremadamente costoso. Las macros de Lisp son diferentes, es una metaprogramación en tiempo de compilación. No está disponible en Python en ninguna forma utilizable.
SK-logic
@ SK-logic Ese tipo de macros rompen la premisa del código es datos , ya que los programas no pueden cambiar las macros (ni siquiera saber de su existencia). En Python, los programas tienen acceso a sus propios árboles de análisis en tiempo de ejecución, si los necesitan. Las macros (preprocesamiento estático) no funcionan en absoluto.
Apalala
-5

Porque Python también puede programar en un estilo no funcional, y eso no es lo suficientemente bueno para el purista de FP. Sin embargo, los programadores más pragmáticos pueden disfrutar de los beneficios del estilo funcional sin ser dogmáticos al respecto:

“El programador funcional suena más bien como un monje medieval, negándose a sí mismo los placeres de la vida con la esperanza de que lo haga virtuoso. Para aquellos más interesados ​​en los beneficios materiales, estas "ventajas" no son muy convincentes. Los programadores funcionales argumentan que hay grandes beneficios materiales ... [pero] esto es simplemente ridículo. Si omitir las declaraciones de asignación traía beneficios tan enormes, los programadores de FORTRAN lo habrían hecho durante veinte años. Es una imposibilidad lógica hacer que un lenguaje sea más poderoso omitiendo funciones, sin importar cuán malas puedan ser ”.

- John Hughes, ¿Por qué es importante la programación funcional?

Mason Wheeler
fuente
66
Estoy de acuerdo. Cualquier idioma sin gotoes terrible.
Anon
2
@Anon: O su hermano grande, call-with-current-continuation.
Chris Jester-Young
44
Mason, el papel que estás citando dice exactamente lo contrario. Su cita es solo un fragmento de un par de párrafos de un artículo que cuenta una historia diferente. De hecho, si citaras ambos párrafos como un todo, mostrarían claramente otro significado.
Marco Mustapic
2
@Mason: sí, el autor afirma que la modularización y la evaluación perezosa son los verdaderos beneficios. Pero tenga en cuenta cómo su cita original estaba fuera de contexto: parece sugerir que el autor está reclamando algo en contra de FP, cuando de hecho citó un párrafo de un documento con la conclusión de que FP es excelente, pero no por la razón engañosa de " menos es más". El título del documento muestra la intención del autor con bastante claridad: "por qué es importante la programación funcional" ;-) Ciertamente no respalda su afirmación de que los lenguajes de FP más puros "son una molestia".
Andres F.
66
@Mason Wheeler: los lenguajes híbridos son anteriores a Python en muchísimo tiempo. Lisp, por ejemplo, data de 1959 y es, de hecho, un lenguaje multi-paradigmático. Es totalmente compatible con enfoques funcionales, enfoques de procedimiento y enfoques de programación orientados a objetos. Con los paquetes de macros correctos en la parte superior, también puede hacer la programación lógica con bastante facilidad. Scheme también es anterior a Python (y este artículo). Se remonta a 1975. Tal vez debería echar un vistazo a esta línea de tiempo de lenguajes de programación en algún momento.
SOLO MI OPINIÓN correcta