functional programming - programacion - Errores/Desventajas de la Programación Funcional
programacion funcional vs orientada a objetos (9)
Aparte de los problemas de velocidad o adopción y abordar un problema más básico, lo he escuchado decir que con la programación funcional, es muy fácil agregar nuevas funciones para los tipos de datos existentes, pero es "difícil" agregar nuevos tipos de datos. Considerar:
(Escrito en SMLnj. Además, disculpe el ejemplo un tanto artificial).
datatype Animal = Dog | Cat;
fun happyNoise(Dog) = "pant pant"
| happyNoise(Cat) = "purrrr";
fun excitedNoise(Dog) = "bark!"
| excitedNoise(Cat) = "meow!";
Puedo agregar muy rápidamente lo siguiente:
fun angryNoise(Dog) = "grrrrrr"
| angryNoise(Cat) = "hisssss";
Sin embargo, si agrego un nuevo tipo a Animal, tengo que pasar por cada función para agregar compatibilidad con ella:
datatype Animal = Dog | Cat | Chicken;
fun happyNoise(Dog) = "pant pant"
| happyNoise(Cat) = "purrrr"
| happyNoise(Chicken) = "cluck cluck";
fun excitedNoise(Dog) = "bark!"
| excitedNoise(Cat) = "meow!"
| excitedNoise(Chicken) = "cock-a-doodle-doo!";
fun angryNoise(Dog) = "grrrrrr"
| angryNoise(Cat) = "hisssss"
| angryNoise(Chicken) = "squaaaawk!";
Sin embargo, tenga en cuenta que todo lo contrario es cierto para los lenguajes orientados a objetos. Es muy fácil agregar una nueva subclase a una clase abstracta, pero puede ser tedioso si desea agregar un nuevo método abstracto a la clase / interfaz abstracta para implementar todas las subclases.
¿Cuándo NO querrías usar programación funcional? ¿En qué no es tan bueno?
Estoy buscando más desventajas del paradigma como un todo, no cosas como "no ampliamente utilizado", o "no hay un buen depurador disponible". Esas respuestas pueden ser correctas a partir de ahora, pero se ocupan de que FP sea un concepto nuevo (un problema inevitable) y no cualidades inherentes.
Relacionado:
Aquí hay algunos problemas que me he encontrado:
- La mayoría de las personas encuentran que la programación funcional es difícil de entender. Esto significa que probablemente sea más difícil para usted escribir un código funcional, y seguramente será más difícil que alguien más lo recoja.
- Los lenguajes de programación funcional suelen ser más lentos de lo que sería un lenguaje como c. Esto se está convirtiendo en un problema menor con el tiempo (porque las computadoras se están volviendo más rápidas, y los compiladores son cada vez más inteligentes)
- Al no estar tan extendidos como sus contrapartes imperativas, puede ser difícil encontrar bibliotecas y ejemplos para problemas comunes de programación. (Por ejemplo, es casi siempre más fácil encontrar algo para Python, entonces es para Haskell)
- Faltan herramientas, especialmente para la depuración. Definitivamente no es tan fácil como abrir Visual Studio para C # o eclipse para Java.
Creo que la mierda que rodea a los lenguajes funcionales es el mayor problema con la programación funcional. Cuando comencé a usar la programación funcional con ira, un gran obstáculo para mí fue comprender por qué muchos de los argumentos altamente evolucionados presentados por la comunidad Lisp (por ejemplo, sobre macros y sintaxis homoicónica) estaban equivocados. Hoy, veo a muchas personas siendo engañadas por la comunidad Haskell con respecto a la programación paralela.
De hecho, no tiene que buscar más allá de este mismo hilo para ver algo de esto:
"En general, los expertos no tienen dificultad para escribir programas funcionales rápidos, y de hecho, algunos de los mejores programas paralelos de 8 y 16 núcleos se escriben ahora en Haskell".
Declaraciones como esta podrían darle la impresión de que los expertos eligen Haskell porque puede ser tan bueno para el paralelismo, pero la verdad es que el rendimiento de Haskell apesta y el mito de que Haskell es bueno para el paralelismo multinúcleo lo perpetúan los investigadores de Haskell con poco o ningún conocimiento sobre el paralelismo que evitan la revisión por pares real al publicar solo dentro de la zona de confort de revistas y conferencias bajo el control de su propia camarilla. Haskell es invisible en el mundo real paralelo / multinúcleo / HPC precisamente porque apesta a la programación paralela.
Específicamente, el verdadero desafío en la programación multinúcleo es aprovechar los cachés de la CPU para garantizar que los núcleos no carezcan de datos, un problema que nunca se ha abordado en el contexto de Haskell. El grupo de Charles Leiserson en el MIT hizo un excelente trabajo explicando y resolviendo este problema utilizando su propio lenguaje Cilk que se convirtió en la columna vertebral de la programación paralela del mundo real para multinúcleo tanto en Intel TBB como en TPL de Microsoft en .NET 4. Hay una excelente descripción de cómo esta técnica se puede utilizar para escribir código imperativo elegante de alto nivel que se compila a código escalable de alto rendimiento en el documento de 2008 La complejidad de caché de algoritmos inconsistentes de caché multiproceso . Lo expliqué en mi reseña de algunas de las investigaciones más avanzadas de Parallel Haskell.
Esto deja un gran interrogante sobre el paradigma de programación puramente funcional. Este es el precio que paga por abstraer el tiempo y el espacio, que siempre fue la principal motivación detrás de este paradigma declarativo.
Es difícil para mí pensar en muchos aspectos negativos de la programación funcional. Por otra parte, soy un ex presidente de la Conferencia Internacional de Programación Funcional, por lo que puede asumir con seguridad que soy parcial.
Creo que los principales inconvenientes tienen que ver con el aislamiento y las barreras de entrada. Aprender a escribir buenos programas funcionales significa aprender a pensar de manera diferente, y hacerlo bien requiere una inversión sustancial de tiempo y esfuerzo . Es difícil de aprender sin un maestro. Estas propiedades conducen a algunos inconvenientes:
Es probable que un programa funcional escrito por un recién llegado sea innecesariamente lento, más probable que, por ejemplo, un programa C escrito por un recién llegado a C. Por otro lado, es igualmente probable que un programa C ++ escrito por un recién llegado será innecesariamente lento. (Todas esas características brillantes ...)
En general, los expertos no tienen dificultad para escribir programas funcionales rápidos; y, de hecho, algunos de los programas paralelos de mejor rendimiento en procesadores de 8 y 16 núcleos ahora están escritos en Haskell .
Es más probable que alguien que comienza la programación funcional se rinda antes de realizar las ganancias de productividad prometidas que alguien que inicie, digamos, Python o Visual Basic. Simplemente no hay tanto apoyo en forma de libros y herramientas de desarrollo.
Hay menos personas con quienes hablar es un buen ejemplo; relativamente pocos programadores de Haskell visitan el sitio regularmente (aunque parte de esto es que los programadores de Haskell tienen sus propios foros animados que son mucho más antiguos y mejor establecidos que ).
También es cierto que no puede hablar con su vecino muy fácilmente, porque los conceptos de programación funcional son más difíciles de enseñar y más difíciles de aprender que los conceptos orientados a objetos detrás de lenguajes como Smalltalk, Ruby y C ++. Y también, la comunidad orientada a objetos ha pasado años desarrollando buenas explicaciones para lo que hacen, mientras que la comunidad de programación funcional parece pensar que su material es obviamente grandioso y no requiere ninguna metáfora o vocabulario especial para explicarlo. (Están equivocados. Todavía estoy esperando el primer gran libro Patrones de diseño funcional .)
Una desventaja bien conocida de la programación funcional perezosa (se aplica a Haskell o Clean, pero no a ML o Scheme o Clojure) es que es muy difícil predecir los costos de tiempo y espacio de evaluar un programa funcional lento , incluso los expertos no pueden hacer eso. Este problema es fundamental para el paradigma y no va a desaparecer. Existen excelentes herramientas para descubrir el comportamiento del tiempo y del espacio a posteriori , pero para utilizarlas de manera efectiva, debe ser experto ya.
Mirando lejos de los detalles de las implementaciones específicas de la programación funcional, veo dos cuestiones clave:
Parece relativamente raro que sea práctico elegir un modelo funcional de algún problema del mundo real por sobre uno imperativo. Cuando el dominio del problema es imperativo, el uso de un lenguaje con esa característica es una elección natural y razonable (ya que, en general, es aconsejable minimizar la distancia entre la especificación y la implementación como parte de la reducción del número de errores sutiles). Sí, esto puede ser superado por un codificador inteligente, pero si necesitas los codificadores de Rock Star para la tarea, es porque es demasiado sangriento.
Por alguna razón que nunca entendí realmente, es mucho más probable que los lenguajes de programación funcionales (o quizás sus implementaciones o comunidades) quieran tener todo en su idioma. Hay mucho menos uso de bibliotecas escritas en otros idiomas. Si alguien más tiene una implementación particularmente buena de alguna operación compleja, tiene mucho más sentido usarla en lugar de hacer la suya propia. Sospecho que esto es en parte una consecuencia del uso de tiempos de ejecución complejos que hacen que el manejo de código extraño (y especialmente hacerlo de manera eficiente) sea bastante difícil. Me encantaría demostrarme que estoy equivocado en este punto.
Supongo que estos dos vuelven a una falta general de pragmatismo causada por una programación funcional que usan mucho más los investigadores de programación que los codificadores comunes. Una buena herramienta puede capacitar a un experto para grandes cosas, pero una gran herramienta es aquella que le permite al hombre común acercarse a lo que un experto puede hacer normalmente, porque esa es, de lejos, la tarea más difícil.
Philip Wadler escribió un artículo sobre esto (llamado Por qué nadie usa los lenguajes de programación funcional) y abordó las trampas prácticas que impiden que las personas usen los lenguajes de FP:
- http://www.cse.iitb.ac.in/~as/fpcourse/sigplan-why.ps.gz
- http://carpanta.dc.fi.udc.es/pf/papers/sigplan-angry.ps.gz
Actualización: enlace antiguo inaccesible para aquellos con acceso ACM:
Si su lenguaje no proporciona buenos mecanismos para sondear el comportamiento de estado / excepción a través de su programa (por ejemplo, azúcares de sintaxis para enlaces monádicos), entonces cualquier tarea que implique estado / excepciones se convierte en una tarea ardua. (Incluso con estos azúcares, algunas personas pueden encontrar más difícil lidiar con el estado / excepciones en FP).
Los modismos funcionales a menudo hacen mucha inversión de control o pereza, lo que a menudo tiene un impacto negativo en la depuración (usando un depurador). (Esto está algo compensado por FP, que es mucho menos propenso a errores debido a la inmutabilidad / transparencia referencial, lo que significa que tendrá que depurar con menos frecuencia).
Solo quería contar una anécdota porque ahora mismo estoy aprendiendo a Haskell mientras hablamos. Estoy aprendiendo Haskell porque me atrae la idea de separar funciones de acciones y hay algunas teorías realmente atractivas detrás de la paralelización implícita debido al aislamiento de las funciones puras de las funciones no puras.
He estado aprendiendo la clase de funciones de fold por tres días. Fold parece tener una aplicación muy simple: tomar una lista y reducirla a un solo valor. Haskell implementa un foldl
y foldr
para esto. Las dos funciones tienen implementaciones masivamente diferentes. Hay una implementación alternativa de foldl
, llamada foldl''
. Además de esto, hay una versión con una sintaxis ligeramente diferente llamada foldr1
y foldl1
con diferentes valores iniciales. De los cuales hay una implementación correspondiente de foldl1''
para foldl1
. Como si todo esto no fuera alucinante, las funciones que fold[lr].*
Requieren como argumentos y usan internamente en la reducción tienen dos firmas separadas, solo una variante trabaja en listas infinitas (r), y solo una de ellas se ejecuta en memoria constante (como entiendo (L) porque solo requiere un redex
). Comprender por qué foldr
puede funcionar en listas infinitas requiere al menos una comprensión decente de los lenguajes lazy-behavoir y el detalle menor de que no todas las funciones forzarán la evaluación del segundo argumento. Los gráficos en línea para estas funciones son confusos para alguien que nunca los vio en la universidad. No hay equivalente perldoc
. No puedo encontrar una sola descripción de lo que hace ninguna de las funciones en el preludio de Haskell. El preludio es una especie de distribución precargada que viene con núcleo. Mi mejor recurso es realmente un hombre que nunca he conocido (Cale) que me está ayudando a costa de su propio tiempo.
Ah, y fold no tiene que reducir la lista a un escalar de tipo sin lista, la función de identidad para las listas se puede escribir foldr (:) [] [1,2,3,4]
(destaca que se puede acumular a una lista).
/ me vuelve a leer.
Una gran desventaja de la programación funcional es que, en un nivel teórico, no coincide con el hardware ni con la mayoría de los lenguajes imperativos. (Esta es la otra cara de una de sus fortalezas obvias, ser capaz de expresar lo que desea hacer en lugar de cómo desea que la computadora lo haga).
Por ejemplo, la programación funcional hace un uso intensivo de la recursión. Esto está bien en el cálculo lambda puro porque el "stack" de las matemáticas es ilimitado. Por supuesto, en hardware real, la pila es muy finita. Recurrir ingenuamente a un gran conjunto de datos puede hacer que su programa explote. La mayoría de los lenguajes funcionales optimizan la recursividad de la cola para que esto no suceda, pero hacer que un algoritmo sea recursivo puede forzarlo a realizar gimnasia codificada (por ejemplo, una función recursiva de la cola crea una lista retrospectiva o tiene que construir una diferencia lista, por lo que tiene que hacer un trabajo extra para volver a una lista mapeada normal en el orden correcto en comparación con la versión no recursiva de cola).
(Gracias a Jared Updike por la sugerencia de la lista de diferencias).