Dado que el lenguaje de máquina (p. Ej. 0110101000110101
), Los lenguajes de computadora generalmente han evolucionado hacia formas más altas de abstracción, lo que facilita la comprensión del código cuando se aplica a un problema. Assembler era una abstracción sobre el código de máquina, C era una abstracción sobre el ensamblador, etc.
El diseño orientado a objetos parece ser muy bueno para permitirnos modelar un problema en términos de objetos, por ejemplo, el problema de un sistema de registro de cursos universitarios puede modelarse con una Course
clase, una Student
clase, etc. Luego, cuando escribimos la solución en un lenguaje OO, tenemos clases similares que tienen responsabilidades y que generalmente son útiles para el diseño, especialmente para modularizar el código. Si le doy este problema a 10 equipos independientes que lo resuelven con un método OO, generalmente las 10 soluciones tendrán en común las clases relacionadas con el problema. Puede haber muchas diferencias cuando comienzas a entrar en el acoplamiento y las interacciones de esas clases, por lo que no existe el "vacío de representación cero".
Mi experiencia con la programación funcional es muy limitada (sin uso en el mundo real, solo programas de tipo Hello World). No puedo ver cómo dichos lenguajes permiten asignar fácilmente soluciones FP a problemas (con una brecha de representación baja) como lo hacen los lenguajes OO.
Entiendo las ventajas de FP con respecto a la programación concurrente. ¿Pero me estoy perdiendo algo, o FP no se trata de reducir una brecha de representación (hacer que las soluciones sean más fáciles de entender)?
Otra forma de preguntar esto: ¿el código FP de 10 equipos diferentes que resuelven el mismo problema del mundo real tiene mucho en común?
De Wikipedia en Abstracción (informática) (énfasis mío):
Los lenguajes de programación funcional exhiben comúnmente abstracciones relacionadas con funciones , tales como abstracciones lambda (convertir un término en una función de alguna variable), funciones de orden superior (los parámetros son funciones), abstracción de corchetes (convertir un término en una función de una variable).
La brecha representacional podría aumentar potencialmente, porque [algunos] problemas del mundo real no se modelan fácilmente con tales abstracciones.
Otra forma en que veo una disminución de la brecha representacional es en el rastreo de los elementos de la solución hasta el problema. Los 0
'sys 1
en el código de máquina son muy difíciles de rastrear, mientras que la Student
clase es fácil de rastrear. No todas las clases de OO se remontan fácilmente al espacio del problema, pero muchas lo hacen.
¿Las abstracciones de FP no siempre necesitan explicarse para descubrir qué parte del espacio del problema están resolviendo (aparte de los problemas de matemáticas )?OK, estoy bien en esta parte. Después de ver muchos más ejemplos, veo cómo las abstracciones de FP son muy claras para las partes del problema que se expresan en el procesamiento de datos.
La respuesta aceptada a una pregunta relacionada ¿ Se puede usar UML para modelar un programa funcional? - dice "Los programadores funcionales no tienen mucho uso para los diagramas". Realmente no me importa si es UML, pero me hace preguntarme si las abstracciones de FP son fáciles de entender / comunicar, si no hay diagramas que se utilicen ampliamente (suponiendo que esta respuesta sea correcta). Una vez más, mi nivel de uso / comprensión de FP es trivial, por lo que entiendo que no hay necesidad de diagramas en programas simples de FP.
El diseño OO tiene niveles de abstracción de función / clase / paquete, con encapsulación (control de acceso, ocultación de información) en cada uno, lo que facilita la gestión de la complejidad. Estos son elementos que permiten pasar del problema a la solución y viceversa.
Muchas respuestas hablan de cómo se realizan el análisis y el diseño en FP de una manera análoga a OO, pero hasta ahora nadie cita nada de alto nivel (Paul citó algunas cosas interesantes, pero es de bajo nivel). Ayer busqué mucho en Google y encontré una discusión interesante. Lo siguiente es de Refactoring Functional Programs de Simon Thompson (2004) (énfasis mío)
Al diseñar un sistema orientado a objetos, se da por sentado que el diseño precederá a la programación. Los diseños se escribirán utilizando un sistema como UML que es compatible con herramientas como Eclipse. Los programadores principiantes pueden aprender un enfoque de diseño visual utilizando sistemas como BlueJ. El trabajo sobre una metodología similar para la programación funcional se informa en FAD: Análisis funcional y diseño , pero existe muy poco trabajo. Puede haber varias razones para esto.
Los programas funcionales existentes son de una escala que no requiere diseño. Muchos programas funcionales son pequeños, pero otros, como el Glasgow Haskell Compiler, son sustanciales.
Los programas funcionales modelan directamente el dominio de la aplicación, lo que hace que el diseño sea irrelevante. Si bien los lenguajes funcionales proporcionan una variedad de abstracciones poderosas, es difícil argumentar que proporcionan todas y solo las abstracciones necesarias para modelar el mundo real.
Los programas funcionales se construyen como una serie de prototipos en evolución.
En la tesis de doctorado citada anteriormente , los beneficios del uso de metodologías de análisis y diseño (ADM) se describen independientemente de los paradigmas. Pero se argumenta que los ADM deben alinearse con el paradigma de implementación. Es decir, OOADM funciona mejor para la programación OO y no se aplica bien a otro paradigma como FP. Aquí hay una gran cita que creo que parafrasea lo que llamo brecha representacional:
se puede discutir extensamente sobre qué paradigma proporciona el mejor soporte para el desarrollo de software, pero se logra el paquete de desarrollo más natural, eficiente y efectivo cuando uno permanece dentro de un solo paradigma desde la descripción del problema hasta la implementación y entrega.
Aquí está el conjunto de diagramas propuestos por FAD:
- diagramas de dependencia de funciones que presentan una función con aquellos que usa en su implementación;
- diagrama de dependencia de tipo que proporciona el mismo servicio para tipos; y,
- diagramas de dependencia del módulo que presentan vistas de la arquitectura del módulo del sistema.
Hay un estudio de caso en la sección 5.1 de la tesis del FAD, que es un sistema para automatizar la producción de datos relacionados con una liga de fútbol (soccer). Los requisitos son 100% funcionales, por ejemplo, ingresar resultados de fútbol, producir tablas de la liga, tablas de puntaje, tablas de asistencia, transferir jugadores entre equipos, actualizar datos después de nuevos resultados, etc. No se menciona ninguna mención de cómo funciona FAD para resolver requisitos no funcionales , además de afirmar que "se debería permitir una nueva funcionalidad a un costo mínimo", algo que es casi imposible de probar.
Lamentablemente, aparte de FAD, no veo ninguna referencia moderna para los lenguajes de modelado (visual) que se proponen para FP. UML es otro paradigma, por lo que debemos olvidarlo.
Respuestas:
Los datos básicos están estructurados de la misma manera en casi cualquier paradigma. Tendrás a
Student
, aCourse
, etc., ya sea un objeto, una estructura, un registro o lo que sea. La diferencia con OOP no es cómo se estructuran los datos, sino cómo se estructuran las funciones.De hecho, encuentro que los programas funcionales se ajustan mucho más a cómo pienso en un problema. Por ejemplo, para planificar el horario de un estudiante para el próximo semestre, piense en cosas como listas de cursos que un estudiante ha completado, cursos en un programa de grado de estudiante, cursos ofrecidos este semestre, cursos para los que un estudiante ha completado requisitos previos, cursos con tiempos que no No conflicto, etc.
De repente, no está tan claro qué clase debería crear y almacenar todas estas listas. Aún menos cuando tienes combinaciones complejas de estas listas. Sin embargo, debes elegir una clase.
En FP, escribe funciones que toman un alumno y una lista de cursos y devuelve una lista filtrada de cursos. Puede agrupar todas esas funciones en un módulo. No tiene que emparejarlo con una clase u otra.
Entonces, sus modelos de datos terminan pareciéndose más a cómo los programadores de OOP piensan en sus modelos, antes de que se contaminen con clases que no tienen otro propósito que proporcionar lugares convenientes para colocar funciones que operan en combinaciones de otras clases. No hay
CourseStudentFilterList
clases extrañas o similares que siempre termines necesitando en OOP, pero nunca pienses en el diseño inicial.fuente
StudentCourseFilterer
para mantener las cosas manejablesfunc
(tal como lo nombróIFilter
, etc.). En general, en FP que le nombrafunc
of
cuando cualquierasort
ofilter
es una opción válida! Por ejemplo, al escribir una función de orden superior que tomafunc
como parámetro.Cuando tomé mi clase de Java hace años, se esperaba que mostraramos nuestras soluciones a toda la clase, así que pude ver cómo piensa la gente; cómo resuelven los problemas lógicamente. Esperaba que las soluciones se agruparan en torno a tres o cuatro soluciones comunes. En cambio, vi como 30 estudiantes resolvieron el problema de 30 maneras completamente diferentes.
Naturalmente, a medida que los programadores novatos adquieran experiencia, obtendrán exposición a patrones de software comunes, comenzarán a usar esos patrones en su código y luego sus soluciones pueden fusionarse en torno a algunas estrategias óptimas. Estos patrones forman un lenguaje técnico mediante el cual los desarrolladores experimentados pueden comunicarse.
El lenguaje técnico que sustenta la programación funcional es la matemática . En consecuencia, los problemas que son más adecuados para resolver utilizando la programación funcional son esencialmente problemas matemáticos. Por matemática, no me estoy refiriendo al tipo de matemática que verías en soluciones de negocios, como sumas y restas. Más bien, estoy hablando del tipo de matemática que podrías ver en Math Overflow, o del tipo de matemática que verías en el motor de búsqueda de Orbitz (está escrito en Lisp).
Esta orientación tiene algunas ramificaciones importantes para los programadores funcionales que intentan resolver problemas de programación del mundo real:
La programación funcional es más declarativa que imperativa; se ocupa principalmente de decirle a la computadora qué hacer, no cómo hacerlo.
Los programas orientados a objetos a menudo se crean de arriba hacia abajo. Se crea un diseño de clase y se completan los detalles. Los programas funcionales a menudo se crean de abajo hacia arriba, comenzando con funciones pequeñas y detalladas que se combinan en funciones de nivel superior.
La programación funcional puede tener ventajas para la creación de prototipos de lógica compleja, la construcción de programas flexibles que pueden cambiar y evolucionar orgánicamente, y la creación de software donde el diseño inicial no está claro.
Los programas orientados a objetos pueden ser más adecuados para dominios de negocios porque las clases, los mensajes entre objetos y los patrones de software proporcionan una estructura que se asigna al dominio de negocios, captura su inteligencia de negocios y lo documenta.
Debido a que todo el software práctico produce efectos secundarios (E / S), los lenguajes de programación puramente funcionales requieren un mecanismo para producir esos efectos secundarios mientras permanecen matemáticamente puros (mónadas).
Los programas funcionales se pueden probar más fácilmente, debido a su naturaleza matemática. El sistema de tipos de Haskell puede encontrar cosas en tiempo de compilación que la mayoría de los lenguajes OO no pueden encontrar.
Y así. Al igual que con muchas cosas en informática, los lenguajes orientados a objetos y los lenguajes funcionales tienen diferentes compensaciones.
Algunos lenguajes OO modernos han adoptado algunos de los conceptos útiles de programación funcional, para que pueda tener lo mejor de ambos mundos. Linq, y las características del lenguaje que se agregaron para admitirlo, es un buen ejemplo de eso.
fuente
SafeString
tipo para representar cadenas que se han escapado de HTML, y en general mantener un mayor seguimiento de efectosMe gustaría enfatizar un aspecto que encuentro importante y que no se ha cubierto en las otras respuestas.
En primer lugar, creo que la brecha de representación entre problemas y soluciones puede estar más en la mente del programador, de acuerdo con sus antecedentes y con los conceptos con los que están más familiarizados.
OOP y FP analizan los datos y las operaciones desde dos perspectivas diferentes y proporcionan diferentes compensaciones, como Robert Harvey ya ha señalado.
Un aspecto importante en el que difieren es la forma en que permiten ampliar su software.
Considere la situación en la que tiene una colección de tipos de datos y una colección de operaciones, por ejemplo, tiene diferentes formatos de imagen y mantiene una biblioteca de algoritmos para procesar imágenes.
La programación funcional hace que sea más fácil agregar nuevas operaciones a su software: solo necesita un cambio local en su código, es decir, agrega una nueva función, que maneja diferentes formatos de datos de entrada. Por otro lado, agregar nuevos formatos está más involucrado: necesita cambiar todas las funciones que ya ha implementado (cambio no local).
El enfoque orientado a objetos es simétrico a esto: cada tipo de datos lleva su propia implementación de todas las operaciones y es responsable de elegir la implementación correcta en tiempo de ejecución (despacho dinámico). Esto facilita agregar un nuevo tipo de datos (por ejemplo, un nuevo formato de imagen): simplemente agrega una nueva clase e implementa todos sus métodos. Por otro lado, agregar una nueva operación significa cambiar todas las clases que necesitan proporcionar esa operación. En muchos idiomas, esto se hace extendiendo una interfaz y adaptando todas las clases que la implementan.
Este enfoque diferente de la extensión es una de las razones por las cuales la OOP es más apropiada para ciertos problemas en los que el conjunto de operaciones varía con menos frecuencia que el conjunto de tipos de datos en los que funcionan estas operaciones. Un ejemplo típico son interfaces gráficas de usuario: usted tiene un conjunto fijo de operaciones que todos los controles deben implementar (
paint
,resize
,move
, etc.) y una colección de widgets que desea extender.Entonces, de acuerdo con esta dimensión, OOP y FP son solo dos formas especulares de organizar su código. Ver SICP , en particular la Sección 2.4.3, Tabla 2.22, y el párrafo Mensaje que pasa .
Resumiendo: en OOP usas datos para organizar operaciones, en FP usas operaciones para organizar datos. Cada enfoque es más fuerte o más débil según el contexto. En general, ninguno de los dos tiene una mayor brecha de representación entre el problema y la solución.
fuente
Other
variante / constructor en su tipo de datos y un parámetro de función adicional para pasar a todas las funciones para manejar el otro caso. Es, por supuesto, incómodo / menos natural con respecto a la solución OOP (despacho dinámico). Del mismo modo, el patrón de visitante es incómodo / menos natural que la solución FP (una función de orden superior).La mayoría de los lenguajes funcionales no están orientados a objetos. Eso no significa que no tengan objetos (en el sentido de tipos complejos que tienen una funcionalidad específica asociada a ellos). Haskell, como Java, tiene listas, mapas, matrices, todo tipo de árboles y muchos otros tipos complejos. Si observa el módulo de Lista o Mapa de Haskell, verá un conjunto de funciones muy similares a los métodos en la mayoría de los módulos de biblioteca OO-language equivalentes. Si inspecciona el código, incluso encontrará una encapsulación similar, con algunos tipos (o sus constructores, para ser precisos) y funciones solo utilizables por otras funciones en el módulo.
La forma en que trabaja con estos tipos en Haskell a menudo no es muy diferente de la manera OO. En Haskell digo
y en Java dices
Tomate, tomate. Las funciones de Haskell no están vinculadas al objeto, pero están estrechamente asociadas con su tipo. "Ah, pero" , dices, "en Java se llama qué método de longitud es apropiado para la clase real de ese objeto". Sorpresa: Haskell también hace polimorfismo con clases de tipos (y otras cosas).
En Haskell, si tengo es una colección de enteros, no importa si la colección es una lista o conjunto o matriz, este código
devolverá una colección con todos los elementos duplicados. Polimorfismo significa que se llamará a la función de mapa apropiada para el tipo específico.
Estos ejemplos no son, en verdad, tipos realmente complejos, pero lo mismo se aplica en problemas más difíciles. La idea de que los lenguajes funcionales no le permiten modelar objetos complejos y asociar funcionalidades específicas con ellos es simplemente errónea.
No hay diferencias importantes y significativas entre el estilo funcional y orientado a objetos, pero yo no lo creo esta respuesta tiene que tratar con ellos. Preguntó si los lenguajes funcionales impiden (u obstaculizan) el modelado intuitivo de problemas y tareas. La respuesta es no.
fuente
FP realmente se esfuerza por reducir la brecha representacional:
Algo que verá mucho en los lenguajes funcionales es la práctica de construir el lenguaje (usando un diseño de abajo hacia arriba) en un lenguaje específico de dominio incorporado (EDSL) . Esto le permite desarrollar un medio de expresar sus preocupaciones comerciales de una manera que sea natural para su dominio dentro del lenguaje de programación. Haskell y Lisp se enorgullecen de esto.
Parte (si no todo) de lo que permite esta capacidad es una mayor flexibilidad y expresividad dentro del idioma o los idiomas base; con funciones de primera clase, funciones de orden superior, composición de funciones y, en algunos idiomas, tipos de datos algebraicos (uniones discriminadas AKA) y la capacidad de definir operadores *, hay más flexibilidad disponible para expresar cosas que con OOP, que significa que probablemente encontrarás formas más naturales de expresar las cosas desde el dominio de tu problema. (Debería decir algo que muchos lenguajes OOP han estado adoptando muchas de estas características recientemente, si no comenzando con ellas).
* Sí, en muchos idiomas OOP puede anular los operadores estándar, pero en Haskell, puede definir otros nuevos.
En mi experiencia trabajando con lenguajes funcionales (principalmente F # y Haskell, algunos Clojure y un poco de Lisp y Erlang), me resulta más fácil mapear el espacio del problema en el lenguaje que con OOP, especialmente con tipos de datos algebraicos, que me parece más flexible que las clases. Algo que me dejó sin aliento cuando comencé con FP, particularmente con Haskell, fue que tenía que pensar / trabajar a un nivel un poco más alto de lo que estaba acostumbrado en los idiomas imperativos o POO; es posible que te encuentres con esto un poco también.
fuente
Como dijo Niklaus Wirth, "Algoritmos + Estructuras de datos = Programas". La programación funcional trata sobre la forma de organizar algoritmos, y no dice mucho sobre cómo organizar estructuras de datos. De hecho, existen lenguajes FP tanto con variables mutables (Lisp) como inmutables (Haskell, Erlang). Si desea comparar y contrastar FP con algo, debe elegir la programación imperativa (C, Java) y declarativa (Prolog).
OOP es, por otro lado, una forma de construir estructuras de datos y adjuntarles algoritmos. FP no le impide construir estructuras de datos similares a las de OOP. Si no me cree, eche un vistazo a las declaraciones de tipo Haskell. Sin embargo, la mayoría de los lenguajes FP están de acuerdo en que las funciones no pertenecen a estructuras de datos y deberían estar en el mismo espacio de nombres. Esto se hace para simplificar la construcción de nuevos algoritmos a través de la composición de funciones. De hecho, si sabe qué entrada toma una función y qué salida produce, ¿por qué debería importar a qué estructura de datos pertenece también? Por ejemplo, ¿por qué
add
se debe llamar a la función en una instancia de tipo Integer y pasar un argumento en lugar de simplemente pasar dos argumentos Integer?Por lo tanto, no veo por qué FP debería hacer que las soluciones sean más difíciles de entender en general, y no creo que lo haga de todos modos. Sin embargo, tenga en cuenta que un programador imperativo experimentado seguramente encontrará los programas funcionales más difíciles de entender que los imperativos, y viceversa. Esto es similar a Windows: la dicotomía de Linux cuando las personas que han invertido 10 años en sentirse cómodos con el entorno de Windows encuentran que Linux es difícil después de un mes de uso, y aquellos que están acostumbrados a Linux no pueden lograr la misma productividad en Windows. Por lo tanto, la 'brecha representacional' de la que estás hablando es muy subjetiva.
fuente
if you know what input a function takes and what output it produces, why should it matter which data structure it belongs too?
Eso se parece mucho a la cohesión, que para cualquier sistema modular es importante. Yo diría que no saber dónde encontrar una función dificultaría la comprensión de una solución, lo que se debe a una mayor brecha de representación. Muchos sistemas OO tienen este problema, pero se debe a un mal diseño (baja cohesión). Una vez más, el problema del espacio de nombres está fuera de mi experiencia en FP, por lo que tal vez no sea tan malo como parece.