La desconexión que ves no es de FP vs. OOP. Se trata principalmente de inmutabilidad y formalismos matemáticos vs. mutabilidad y enfoques informales.
Primero, dejemos de lado el problema de la mutabilidad: puede tener FP con mutabilidad y OOP con inmutabilidad bien. Aún más funcional que Haskell te permite jugar con datos mutables todo lo que quieras, solo tienes que ser explícito sobre qué es mutable y el orden en que suceden las cosas; y dejando a un lado las preocupaciones sobre la eficiencia, casi cualquier objeto mutable podría construir y devolver una nueva instancia "actualizada" en lugar de cambiar su propio estado interno.
El problema más grande aquí son los formalismos matemáticos, en particular el uso intensivo de tipos de datos algebraicos en un lenguaje poco alejado del cálculo lambda. Has etiquetado esto con Haskell y F #, pero te das cuenta de que es solo la mitad del universo de programación funcional; la familia Lisp tiene un carácter muy diferente, mucho más libre en comparación con los lenguajes de estilo ML. La mayoría de los sistemas OO de amplio uso en la actualidad son de naturaleza muy informal: existen formalismos para OO, pero no se mencionan explícitamente de la misma forma que los formalismos de FP en los lenguajes de estilo ML.
Muchos de los conflictos aparentes simplemente desaparecen si elimina la discrepancia de formalismo. ¿Desea construir un sistema OO flexible, dinámico y ad-hoc sobre un Lisp? Adelante, funcionará bien. ¿Desea agregar un sistema OO formal e inmutable a un lenguaje ML-style? No hay problema, simplemente no esperes que funcione bien con .NET o Java.
Ahora, usted puede preguntarse, ¿qué es un formalismo apropiado para programación orientada a objetos? Bueno, aquí está la línea de golpe: en muchos sentidos, ¡está más centrada en las funciones que la FP de estilo ML! Volveré a referirme al one of my favorite papers por lo que parece ser la distinción clave: los datos estructurados como los tipos de datos algebraicos en los lenguajes de estilo ML proporcionan una representación concreta de los datos y la capacidad de definir operaciones en él; los objetos proporcionan una abstracción de recuadro negro sobre el comportamiento y la capacidad de reemplazar fácilmente los componentes.
Hay una dualidad aquí que va más allá que solo FP vs. OOP: Está estrechamente relacionada con lo que algunos teóricos del lenguaje de programación llaman the Expression Problem: Con datos concretos, puede agregar fácilmente nuevas operaciones que trabajan con él, pero cambiando la estructura de los datos Es más dificil. Con los objetos, puede agregar fácilmente nuevos datos (por ejemplo, nuevas subclases) pero es difícil agregar nuevas operaciones (piense en agregar un nuevo método abstracto a una clase base con muchos descendientes).
La razón por la que digo que la OOP está más centrada en las funciones es que las mismas funciones representan una forma de abstracción conductual. De hecho, puede simular la estructura de estilo OO en algo como Haskell utilizando registros que contienen un conjunto de funciones como objetos, permitiendo que el tipo de registro sea una "interfaz" o una "clase base abstracta" y que las funciones que crean registros reemplacen constructores de clase. Entonces, en ese sentido, los lenguajes OO usan funciones de orden superior mucho, mucho más a menudo que, por ejemplo, Haskell.
Para ver un ejemplo de algo así como este tipo de diseño muy bien utilizado en Haskell, lea la fuente de the graphics-drawingcombinators package, en particular la forma en que utiliza un tipo de registro opaco que contiene funciones y combina cosas solo en términos de su comportamiento.
EDIT: Un par de cosas finales Me olvidé de mencionar anteriormente.
Si OO hace un uso extensivo de funciones de orden superior, al principio podría parecer que debería encajar de forma muy natural en un lenguaje funcional como Haskell. Lamentablemente, este no es el caso. Es cierto que los objetos como los describí (véase el documento mencionado en el enlace LtU) encajan perfectamente. de hecho, el resultado es un estilo OO más puro que la mayoría de los lenguajes OO, porque los "miembros privados" están representados por valores ocultos por el cierre utilizado para construir el "objeto" y son inaccesibles a cualquier otra cosa que no sea la instancia específica. ¡No se obtiene mucho más privado que eso!
Lo que no funciona muy bien en Haskell es subtipificación. Y, aunque creo que la herencia y la subtipificación se usan con demasiada frecuencia en los lenguajes OO, alguna forma de subtipado es bastante útil para poder combinar objetos de manera flexible. Haskell carece de una noción inherente de subtipado, y los reemplazos hechos a mano tienden a ser excesivamente torpes para trabajar con ellos.
Como un lado, la mayoría de los lenguajes OO con sistemas de tipo estático también hacen un hash completo de subtipado al ser demasiado flojos con la posibilidad de sustitución y no proporcionar soporte adecuado para la variación en las firmas de métodos. De hecho, creo que el único lenguaje completo de OO que no lo ha estropeado completamente, al menos que yo sepa, es Scala (F # parecía hacer demasiadas concesiones a .NET, aunque al menos no creo hace nuevos errores). Sin embargo, tengo una experiencia limitada con muchos de esos idiomas, así que definitivamente podría estar equivocado aquí.
En una nota específica de Haskell, sus "clases de tipos" a menudo parecen tentadores para los programadores OO, a lo que les digo: No vayan allí. Tratar de implementar OOP de esa manera solo terminará en lágrimas. Piense en clases de tipos como reemplazo de funciones/operadores sobrecargados, no OOP.
+1, ¡Gran respuesta! – missingfaktor
Gracias por la impresionante respuesta (y los enlaces). Probablemente tendré preguntas de seguimiento después de leer el material. –