¿La programación funcional es un superconjunto de objetos orientados?

26

Mientras más programación funcional hago, más me parece que agrega una capa adicional de abstracción que parece como la capa de una cebolla, que abarca todas las capas anteriores.

No sé si esto es cierto, así que, dejando de lado los principios de OOP con los que he trabajado durante años, ¿alguien puede explicar qué tan funcional representa o no con precisión alguno de ellos: encapsulación, abstracción, herencia, polimorfismo?

Creo que todos podemos decir, sí, tiene encapsulación a través de tuplas, o las tuplas cuentan técnicamente como un hecho de "programación funcional" o ¿son solo una utilidad del lenguaje?

Sé que Haskell puede cumplir con el requisito de "interfaces", pero de nuevo, ¿no está seguro si su método es funcional? Supongo que el hecho de que los functores tienen una base matemática podría decirse que son una construcción definitiva con la expectativa de ser funcional, ¿tal vez?

Por favor, detalle cómo cree que funcional cumple o no los 4 principios de OOP.

Editar: entiendo muy bien las diferencias entre el paradigma funcional y el paradigma orientado a objetos y me doy cuenta de que hay muchos lenguajes multiparadigm en estos días que pueden hacer ambas cosas. Realmente solo estoy buscando definiciones de cómo fp (piense purista, como Haskell) puede hacer cualquiera de las 4 cosas enumeradas, o por qué no puede hacer ninguna de ellas. es decir, "La encapsulación se puede hacer con cierres" (o si estoy equivocado en esta creencia, indique por qué).

Jimmy Hoffa
fuente
77
Esos 4 principios no "hacen" POO. OOP simplemente "resuelve" esos mediante el uso de clases, la jerarquía de clases y sus instancias. Pero también me gustaría una respuesta si hay formas de lograrlos en la programación funcional.
Eufórico
2
@Euphoric Dependiendo de la definición, hace POO.
Konrad Rudolph el
2
@KonradRudolph Sé que muchas personas afirman estas cosas y los beneficios que aportan como propiedades únicas de OOP. Suponiendo que "polimorfismo" significa "polimorfismo de subtipo", puedo ir con los dos últimos integrales a OOP. Pero todavía tengo que encontrar una definición útil de encapsulación y abstracción que excluya los enfoques decididamente que no son OOP. Puede ocultar detalles y limitar el acceso a ellos bien incluso en Haskell. Y el Haskell también tiene polimorfismo ad-hoc, simplemente no polimorfismo de subtipo: la pregunta es, ¿importa el bit "subtipo"?
1
@KonradRudolph Eso no lo hace más aceptable. En todo caso, es un incentivo para dar un paso adelante y dar a quienes lo difunden razones para reconsiderarlo.
1
La abstracción es intrínseca a cualquier programación, al menos cualquier programación más allá del código máquina sin procesar. La encapsulación ha existido mucho antes de OOP, y es intrínseca a la programación funcional. No se requiere un lenguaje funcional para incluir sintaxis explícita para herencia o polimorfismo. Supongo que eso se suma a 'no'.
sdenham

Respuestas:

44

La programación funcional no es una capa por encima de OOP; Es un paradigma completamente diferente. Es posible hacer OOP en un estilo funcional (F # fue escrito exactamente para este propósito), y en el otro extremo del espectro tienes cosas como Haskell, que rechaza explícitamente los principios de orientación a objetos.

Puede realizar la encapsulación y la abstracción en cualquier lenguaje lo suficientemente avanzado como para admitir módulos y funciones. OO proporciona mecanismos especiales para la encapsulación, pero no es algo inherente a OO. El punto de OO es el segundo par que mencionaste: herencia y polimorfismo. El concepto se conoce formalmente como sustitución de Liskov, y no se puede obtener sin el soporte de nivel de lenguaje para la programación orientada a objetos. (Sí, es posible fingirlo en algunos casos, pero pierde muchas de las ventajas que OO aporta).

La programación funcional no se centra en la sustitución de Liskov. Se enfoca en aumentar el nivel de abstracción y en minimizar el uso de estado mutable y rutinas con "efectos secundarios", que es un término que a los programadores funcionales les gusta usar para hacer que las rutinas que realmente hacen algo (en lugar de simplemente calcular algo) suenen de miedo. Pero, de nuevo, son paradigmas completamente separados, que se pueden usar juntos o no, dependiendo del lenguaje y la habilidad del programador.

Mason Wheeler
fuente
1
Bueno, la herencia (en esos casos excepcionalmente raros cuando se necesita) se puede lograr sobre la composición, y es más limpia que la herencia a nivel de tipo. El polimorfismo es natural, especialmente en presencia de tipos polimórficos. Pero, por supuesto, estoy de acuerdo en que FP no tiene nada que ver con OOP y sus principios.
SK-logic
Siempre es posible falsificarlo: puede implementar objetos en el idioma que elija. Aunque estoy de acuerdo con todo lo demás :)
Eliot Ball
55
No creo que el término "efectos secundarios" haya sido acuñado (o se usa principalmente) por programadores funcionales.
sepp2k
44
@ sepp2k: No dijo que inventaron el término, solo que lo manejan usando aproximadamente el mismo tono que uno normalmente usaría para referirse a los niños que se niegan a salir de su césped.
Aaronaught
16
@Aaronaught, no son los niños en mi césped los que me molestan, ¡son sus efectos secundarios sangrientos! Si simplemente dejaran de mutar por todo mi césped, no me importaría en absoluto.
Jimmy Hoffa
10

La siguiente intuición me parece útil para comparar OOP y FP.

En lugar de considerar FP como un superconjunto de OOP, piense en OOP y FP como dos formas alternativas de mirar un modelo de cálculo subyacente similar en el que tiene:

  1. Alguna operación que se ejecuta,
  2. algunos argumentos de entrada a la operación,
  3. algunos datos / parámetros fijos que pueden influir en la definición de la operación,
  4. algún valor de resultado, y
  5. posiblemente un efecto secundario.

En OOP esto es capturado por

  1. Un método que se ejecuta,
  2. los argumentos de entrada de un método,
  3. el objeto en el que se invoca el método, que contiene algunos datos locales en forma de variables miembro,
  4. el valor de retorno del método (posiblemente nulo),
  5. Los efectos secundarios del método.

En FP esto es capturado por

  1. Un cierre que se ejecuta,
  2. los argumentos de entrada del cierre,
  3. las variables capturadas del cierre,
  4. el valor de retorno del cierre,
  5. los posibles efectos secundarios del cierre (en lenguajes puros como Haskell, esto sucede de manera muy controlada).

Con esta interpretación, un objeto puede verse como una colección de cierres (sus métodos) que capturan todas las mismas variables no locales (las variables miembro del objeto son comunes a todos los cierres de la colección). Esta vista también está respaldada por el hecho de que, en lenguajes orientados a objetos, los cierres a menudo se modelan como objetos con exactamente un método.

Creo que los diferentes puntos de vista se originan en el hecho de que la vista orientada a objetos se centra en los objetos (los datos) mientras que la vista funcional se centra en las funciones / cierres (las operaciones).

Giorgio
fuente
8

Depende de a quién le pidas una definición de POO. Pregúntele a cinco personas y probablemente obtendrá seis definiciones. Wikipedia dice :

Los intentos de encontrar una definición de consenso o teoría detrás de los objetos no han tenido mucho éxito.

Entonces, cuando alguien dé una respuesta muy definitiva, tómela con un grano de sal.

Dicho esto, hay un buen argumento para argumentar que sí, FP es un superconjunto de OOP como paradigma. En particular, la definición de Alan Kay del término programación orientada a objetos no contradice esta noción (pero la de Kristen Nygaard sí ). Todo lo que le preocupaba a Kay era que todo es un objeto, y que la lógica se implementa al pasar mensajes entre objetos.

Quizás lo más interesante para su pregunta es que las clases y los objetos pueden considerarse en términos de funciones y cierres devueltos por funciones (que actúan como clases y constructores a la vez). Esto se acerca mucho a la programación basada en prototipos, y de hecho JavaScript permite hacer precisamente eso.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Por supuesto, JavaScript permite la mutación de valores que es ilegal en la programación puramente funcional, pero tampoco se requiere en una definición estricta de OOP).

Sin embargo, la pregunta más importante es: ¿Es esta una clasificación significativa de OOP? ¿Es útil pensar en él como un subconjunto de programación funcional? Creo que en la mayoría de los casos, no lo es.

Konrad Rudolph
fuente
1
Siento que puede ser significativo pensar en dónde debe trazarse la línea cuando cambio de paradigma. Si se dice que no hay forma de lograr el polimorfismo subtípico en fp, entonces no me molestaré en tratar de usar fp en el modelado de algo que encaje bien con él. Sin embargo, si es posible, puedo tomarme el tiempo para lograr una buena forma de hacerlo (aunque una buena forma puede no ser posible) cuando trabajo mucho en un espacio fp pero quiero polimorfismo subtípico en algunos espacios de nicho. Obviamente, si la mayoría del sistema encaja con él, sería mejor usar OOP.
Jimmy Hoffa
6

FP como OO no es un término bien definido. Hay escuelas con definiciones diferentes, a veces conflictivas. Si toma lo que tienen en común, se reduce a:

  • programación funcional es programación con funciones de primera clase

  • La programación OO es la programación con polimorfismo de inclusión combinado con al menos una forma restringida de sobrecarga resuelta dinámicamente. (Una nota al margen: en los círculos OO, el polimorfismo generalmente se considera polimorfismo de inclusión , mientras que las escuelas FP generalmente significa polimorfismo paramétrico ).

Todo lo demás está presente en otro lugar o ausente en algunos casos.

FP y OO son dos herramientas de construcción de abstracciones. Cada uno tiene sus propias fortalezas y debilidades (por ejemplo, tienen una dirección de extensión preferida diferente en el problema de expresión), pero ninguna es intrínsecamente más poderosa que la otra. Puede construir un sistema OO sobre un núcleo FP (CLOS es uno de esos sistemas). Puede usar un marco OO para obtener funciones de primera clase (vea la forma en que las funciones lambda se definen en C ++ 11, por ejemplo).

Un programador
fuente
Creo que te refieres a 'funciones de primera clase' en lugar de 'funciones de primer orden'.
dan_waterworth
Errr ... C ++ 11 lambdas son apenas funciones de primera clase: cada lambda tiene su propio tipo ad-hoc (a todos los efectos prácticos, una estructura anónima), incompatible con un tipo de puntero de función nativa. Y std::function, a la que se pueden asignar tanto punteros de función como lambdas, es decididamente genérico, no orientado a objetos. Esto no es sorprendente, porque la marca limitada de polimorfismo de la orientación a objetos (polimorfismo de subtipo) es estrictamente menos poderosa que el polimorfismo paramétrico (incluso Hindley-Milner, y mucho menos el Sistema F-omega completo).
pyon
No tengo mucha experiencia con lenguajes funcionales puristas, pero si puedes definir clases de un método estático dentro de cierres y pasarlas a diferentes contextos, diría que estás (torpemente quizás) al menos a mitad de camino Hay opciones de estilo funcional. Hay muchas formas de evitar los parámetros estrictos en la mayoría de los idiomas.
Erik Reppen
2

No; OOP puede verse como un superconjunto de programación procesal y difiere fundamentalmente del paradigma funcional porque tiene un estado representado en los campos de instancia. En el paradigma funcional, las variables son funciones que se aplican sobre los datos constantes para obtener el resultado deseado.

En realidad, puede considerar la programación funcional como un subconjunto de OOP; Si hace que todas sus clases sean inmutables, puede considerar que tiene algún tipo de programación funcional.

m3th0dman
fuente
3
Las clases inmutables no realizan funciones de orden superior, listas de comprensiones o cierres. Fp no es un subconjunto.
Jimmy Hoffa
1
@Jimmy Hoffa: puede simular fácilmente una función de orden superior creando una clase que tenga un método único que tome o más objetos de un tipo similar y también devuelva un objeto de este tipo similar (tipo que tiene un método y no campos) . La comprensión de la lista no es algo relacionado con el lenguaje de programación ni el paradigma (Smalltalk lo admite y es POO). Los cierres están presentes en C # y también se insertarán en Java.
m3th0dman
sí C # tiene cierres, pero eso se debe a que es multi-paradigma, se agregaron cierres junto con otras piezas de fp a C # (por lo que estoy eternamente agradecido) pero su presencia en un lenguaje oop no los hace demasiado. Sin embargo, lo bueno de la función de orden superior es que la encapsulación de un método en una clase permite el mismo comportamiento.
Jimmy Hoffa
2
Sí, pero si usa los cierres de uso para alterar el estado, ¿todavía programaría en un paradigma funcional? El punto es: el paradigma funcional se trata de la falta de estado, no de funciones de alto orden, recursividad o cierres.
m3th0dman
pensamiento interesante sobre la definición de fp .. Tendré que pensar más sobre esto, gracias por compartir sus observaciones.
Jimmy Hoffa
2

Responder:

Wikipedia tiene un excelente artículo sobre programación funcional con algunos de los ejemplos que solicita. @Konrad Rudolph ya proporcionó el enlace al artículo de OOP .

No creo que un paradigma sea un superconjunto del otro. Son diferentes perspectivas sobre la programación y algunos problemas se resuelven mejor desde una perspectiva y otros desde otra.

Su pregunta se complica aún más por todas las implementaciones de FP y OOP. Cada idioma tiene sus propias peculiaridades que son relevantes para cualquier buena respuesta a su pregunta.

Divagación cada vez más tangencial:

Me gusta la idea de que un lenguaje como Scala intente darte lo mejor de ambos mundos. Me preocupa que también te dé las complicaciones de ambos mundos.

Java es un lenguaje OO, pero la versión 7 agregó una función de "prueba con recursos" que puede usarse para imitar una especie de cierre. Aquí imita la actualización de una variable local "a" en el medio de otra función, sin que sea visible para esa función. En este caso, la primera mitad de la otra función es el constructor Closure Try () y la segunda mitad es el método close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Salida:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Esto podría ser útil para su propósito de abrir una secuencia, escribir en la secuencia y cerrarla de manera confiable, o simplemente para emparejar dos funciones de una manera que no se olvide de llamar a la segunda después de hacer un trabajo entre ellas. . Por supuesto, es tan nuevo e inusual que otro programador podría eliminar el bloque de prueba sin darse cuenta de que está rompiendo algo, por lo que actualmente es una especie de antipatrón, pero es interesante que se pueda hacer.

Puede expresar cualquier bucle en la mayoría de los idiomas imperativos como una recursividad. Los objetos y las variables pueden hacerse inmutables. Se pueden escribir procedimientos para minimizar los efectos secundarios (aunque yo diría que una verdadera función no es posible en una computadora: el tiempo que lleva ejecutar y los recursos del procesador / disco / sistema que consume son efectos secundarios inevitables). Se puede hacer que algunos lenguajes funcionales también realicen muchas, si no todas, las operaciones orientadas a objetos. No tienen que ser mutuamente excluyentes, aunque algunos lenguajes tienen limitaciones (como no permitir ninguna actualización de variables) que impiden ciertos patrones (como los campos mutables).

Para mí, las partes más útiles de la programación orientada a objetos son la ocultación de datos (encapsulación), el tratamiento de objetos suficientemente similares como el mismo (polimorfismo) y la recopilación de sus datos y métodos que operan en esos datos juntos (objetos / clases). La herencia puede ser el buque insignia de OOP, pero para mí es la parte menos importante y menos utilizada.

Las partes más útiles de la programación funcional son la inmutabilidad (tokens / valores en lugar de variables), funciones (sin efectos secundarios) y cierres.

No creo que esté orientado a objetos, pero tengo que decir que una de las cosas más útiles en informática es la capacidad de declarar una interfaz, luego tener varias piezas de funcionalidad y datos para implementar esa interfaz. También me gusta tener algunos datos mutables con los que trabajar, así que supongo que no me siento totalmente cómodo en lenguajes exclusivamente funcionales, aunque trato de limitar la mutabilidad y los efectos secundarios en todos los diseños de mis programas.

GlenPeterson
fuente