Últimamente me han interesado algunos de los conceptos de programación funcional. He usado OOP desde hace algún tiempo. Puedo ver cómo construiría una aplicación bastante compleja en OOP. Cada objeto sabría cómo hacer las cosas que hace ese objeto. O cualquier cosa que haga la clase de padres también. Entonces puedo decirle simplemente que hable Person().speak()
a la persona.
Pero, ¿cómo hago cosas similares en la programación funcional? Veo cómo las funciones son elementos de primera clase. Pero esa función solo hace una cosa específica. ¿Simplemente tendría un say()
método flotando y lo llamaría con un equivalente de Person()
argumento para saber qué tipo de cosa está diciendo algo?
Entonces puedo ver las cosas simples, ¿cómo haría lo comparable de OOP y los objetos en la programación funcional, para poder modularizar y organizar mi base de código?
Como referencia, mi experiencia principal con OOP es Python, PHP y algunos C #. Los idiomas que estoy viendo que tienen características funcionales son Scala y Haskell. Aunque me estoy inclinando hacia Scala.
Ejemplo básico (Python):
Animal(object):
def say(self, what):
print(what)
Dog(Animal):
def say(self, what):
super().say('dog barks: {0}'.format(what))
Cat(Animal):
def say(self, what):
super().say('cat meows: {0}'.format(what))
dog = Dog()
cat = Cat()
dog.say('ruff')
cat.say('purr')
Respuestas:
Lo que realmente está preguntando aquí es cómo hacer el polimorfismo en lenguajes funcionales, es decir, cómo crear funciones que se comporten de manera diferente en función de sus argumentos.
Tenga en cuenta que el primer argumento de una función suele ser equivalente al "objeto" en OOP, pero en los lenguajes funcionales generalmente desea separar las funciones de los datos, por lo que es probable que el "objeto" sea un valor de datos puro (inmutable).
Los lenguajes funcionales en general proporcionan varias opciones para lograr el polimorfismo:
Como ejemplo, aquí hay una implementación de Clojure de su problema utilizando métodos múltiples:
Tenga en cuenta que este comportamiento no requiere que se definan clases explícitas: los mapas regulares funcionan bien. La función de despacho (: tipo en este caso) podría ser cualquier función arbitraria de los argumentos.
fuente
Esta no es una respuesta directa, ni es necesariamente 100% precisa, ya que no soy un experto en lenguaje funcional. Pero en cualquier caso, compartiré contigo mi experiencia ...
Hace aproximadamente un año estaba en un barco similar al tuyo. He hecho C ++ y C # y todos mis diseños siempre fueron muy pesados en OOP. Escuché acerca de los lenguajes FP, leí información en línea, hojeé el libro de F #, pero aún no pude comprender cómo un lenguaje FP puede reemplazar a OOP o ser útil en general, ya que la mayoría de los ejemplos que he visto son demasiado simples.
Para mí, el "avance" se produjo cuando decidí aprender Python. Descargué python, luego fui a la página de inicio del proyecto euler y comencé a resolver un problema tras otro. Python no es necesariamente un lenguaje FP y ciertamente puedes crear clases en él, pero en comparación con C ++ / Java / C #, tiene muchas más construcciones FP, así que cuando comencé a jugar con él, tomé una decisión consciente de no hacerlo. definir una clase a menos que sea absolutamente necesario.
Lo que encontré interesante sobre Python fue lo fácil y natural que era tomar funciones y "unirlas" para crear funciones más complejas y al final su problema aún se resolvió llamando a una sola función.
Usted señaló que al codificar debe seguir el principio de responsabilidad única y eso es absolutamente correcto. Pero el hecho de que la función sea responsable de una sola tarea no significa que solo pueda hacer el mínimo absoluto. En FP, todavía tienes niveles de abstracción. Por lo tanto, sus funciones de nivel superior aún pueden hacer "una" cosa, pero pueden delegar a funciones de nivel inferior para implementar detalles más precisos de cómo se logra esa "una" cosa.
Sin embargo, la clave con FP es que no tiene efectos secundarios. Siempre que trate la aplicación como una simple transformación de datos con un conjunto definido de entradas y un conjunto de salidas, puede escribir un código FP que logre lo que necesita. Obviamente, no todas las aplicaciones encajarán bien en este molde, pero una vez que comience a hacerlo, se sorprenderá de cuántas aplicaciones encajan. Y aquí es donde creo que Python, F # o Scala brillan porque te dan construcciones de FP, pero cuando necesitas recordar tu estado e "introducir efectos secundarios", siempre puedes recurrir a técnicas OOP verdaderas y probadas.
Desde entonces, he escrito un montón de código de Python como utilidades y otros scripts de ayuda para el trabajo interno y algunos de ellos se ampliaron bastante lejos, pero al recordar los principios básicos de SOLID, la mayoría de ese código salió muy mantenible y flexible. Al igual que en OOP, su interfaz es una clase y mueve las clases a medida que refactoriza y / o agrega funcionalidad, en FP hace exactamente lo mismo con las funciones.
La semana pasada comencé a codificar en Java y desde entonces, casi a diario, me recuerdan que cuando estoy en OOP, tengo que implementar interfaces declarando clases con métodos que anulan funciones, en algunos casos podría lograr lo mismo en Python usando un La expresión lambda simple, por ejemplo, 20-30 líneas de código que escribí para escanear un directorio, habría sido de 1-2 líneas en Python y sin clases.
FP en sí mismos son idiomas de nivel superior. En Python (lo siento, mi única experiencia de FP) podría reunir la comprensión de la lista dentro de otra comprensión de la lista con lambdas y otras cosas agregadas y todo sería solo 3-4 líneas de código. En C ++, podría lograr absolutamente lo mismo, pero debido a que C ++ es de nivel inferior, tendría que escribir mucho más código que 3-4 líneas y a medida que aumenta el número de líneas, mi entrenamiento SRP comenzaría y comenzaría pensando en cómo dividir el código en partes más pequeñas (es decir, más funciones). Pero en aras de la mantenibilidad y la ocultación de los detalles de implementación, pondría todas esas funciones en la misma clase y las haría privadas. Y ahí lo tienen ... Acabo de crear una clase, mientras que en Python habría escrito "return (.... lambda x: .. ....)"
fuente
En Haskell, lo más cercano que tienes es "clase". Esta clase, aunque no es igual a la clase en Java y C ++ , funcionará para lo que desea en este caso.
En su caso, así es como se verá su código.
Entonces puede tener tipos de datos individuales adaptando estos métodos.
EDITAR: - Antes de que pueda especializarse, digamos para Dog, debe decirle al sistema que Dog es animal.
EDITAR: - Para Wilq.
Ahora, si quieres usar say en una función say foo, tendrás que decirle a haskell que foo solo puede trabajar con Animal.
ahora, si llamas a foo con perro, ladrará, si llamas con gato, maullaría.
Ahora no puede tener ninguna otra definición de función, por ejemplo. Si se llama a say con algo que no sea animal, provocará un error de compilación.
fuente
Los lenguajes funcionales usan 2 construcciones para lograr el polimorfismo:
Crear código polimórfico con esos es completamente diferente de cómo OOP usa la herencia y los métodos virtuales. Si bien ambos pueden estar disponibles en su lenguaje OOP favorito (como C #), la mayoría de los lenguajes funcionales (como Haskell) aumentan a once. Es raro que la función sea no genérica y la mayoría de las funciones tienen funciones como parámetros.
Es difícil de explicar de esta manera y requerirá mucho tiempo aprender esta nueva forma. Pero para hacer esto, debe olvidar completamente la POO, porque no es así como funciona en el mundo funcional.
fuente
realmente depende de lo que quieras lograr.
Si solo necesita una forma de organizar el comportamiento en función de criterios selectivos, puede utilizar, por ejemplo, un diccionario (tabla hash) con objetos de función. en python podría ser algo como:
Sin embargo, tenga en cuenta que (a) no hay 'instancias' de perros o gatos y (b) tendrá que hacer un seguimiento del 'tipo' de sus objetos usted mismo.
como por ejemplo:
pets = [['martin','dog','grrrh'], ['martha', 'cat', 'zzzz']]
. entonces podrías hacer una lista de comprensión como[animals[pet[1]]['say'](pet[2]) for pet in pets]
fuente
Los idiomas OO se pueden usar en lugar de idiomas de bajo nivel a veces para interactuar directamente con una máquina. C ++ Seguro, pero incluso para C # hay adaptadores y demás. Aunque escribir código para controlar las partes mecánicas y tener un control minucioso sobre la memoria se mantiene lo más cerca posible del nivel más bajo posible. Pero si esta pregunta está relacionada con el software actual orientado a objetos como Line Of Business, aplicaciones web, IOT, servicios web y la mayoría de las aplicaciones de uso masivo, entonces ...
Respuesta, si corresponde
Los lectores pueden intentar trabajar con una arquitectura orientada a servicios (SOA). Es decir, DDD, N-Layered, N-Tiered, Hexagonal, lo que sea. No he visto que una aplicación para grandes empresas utilice eficientemente OO "tradicional" (Active-Record o Rich-Models) como se describió en los años 70 y 80 en la última década. (Ver nota 1)
La falla no es con el OP, pero hay un par de problemas con la pregunta.
El ejemplo que proporciona es simplemente para demostrar Polimorfismo, no es un código de producción. A veces, ejemplos exactamente así se toman literalmente.
En FP y SOA, los datos se separan de la lógica de negocios. Es decir, los datos y la lógica no van juntos. La lógica entra en Servicios, y los Datos (Modelos de dominio) no tienen comportamiento Polimórfico (Ver Nota 2).
Los servicios y funciones pueden ser polimórficos. En FP, con frecuencia pasa funciones como parámetros a otras funciones en lugar de valores. Puede hacer lo mismo en OO Languages con tipos como Callable o Func, pero no se ejecuta desenfrenada (Ver Nota 3). En FP y SOA, sus modelos no son polimórficos, solo sus servicios / funciones. (Ver nota 4)
Hay un mal caso de hardcoding en ese ejemplo. No solo estoy hablando de la cadena de color rojo "perro ladra". También estoy hablando de CatModel y DogModel. ¿Qué sucede cuando quieres agregar una oveja? ¿Tienes que ingresar tu código y crear un nuevo código? ¿Por qué? En el código de producción, preferiría ver solo un AnimalModel con sus propiedades. En el peor de los casos, un modelo de anfibio y un modelo de ave si sus propiedades y manejo son tan diferentes.
Esto es lo que esperaría ver en un lenguaje "OO" actual:
¿Cómo pasar de las clases en OO a la programación funcional? Como otros han dicho; Puedes, pero en realidad no. El objetivo de lo anterior es demostrar que ni siquiera debería usar Clases (en el sentido tradicional del mundo) al hacer Java y C #. Una vez que llegue a escribir código en una Arquitectura Orientada a Servicios (DDD, Capas, Niveles, Hexagonal, lo que sea), estará un paso más cerca de Funcional porque separa sus Datos (Modelos de Dominio) de sus Funciones Lógicas (Servicios).
OO Language un paso más cerca de FP
Incluso podría llevarlo un poco más lejos y dividir sus Servicios SOA en dos tipos.
Clase opcional Tipo 1 : Servicios comunes de implementación de interfaz para puntos de entrada. Estos serían puntos de entrada "impuros" que pueden llamar a otras funciones "puras" o "impuras". Estos podrían ser sus puntos de entrada de una API RESTful.
Clase opcional tipo 2 : Servicios de lógica empresarial pura. Estas son clases estáticas que tienen funcionalidad "pura". En FP, "Puro" significa que no hay efectos secundarios. No establece explícitamente el estado o la persistencia en ninguna parte. (Ver nota 5)
Entonces, cuando piensa en las Clases en Lenguajes Orientados a Objetos, que se usan en una Arquitectura Orientada a Servicios, no solo beneficia su Código OO, sino que comienza a hacer que la Programación Funcional parezca muy fácil de entender.
Notas
Nota 1 : El diseño orientado a objetos "Rich" o "Active-Record" original todavía existe. Hay MUCHO código heredado como ese cuando la gente "lo hacía bien" hace una década o más. La última vez que vi ese tipo de código (hecho correctamente) era de un video juego Codebase en C ++ donde controlaban la memoria con precisión y tenían un espacio muy limitado. No quiere decir que FP y las arquitecturas orientadas a servicios sean bestias y no deberían considerar el hardware. Pero colocan la capacidad de cambiar constantemente, mantenerse, tener tamaños de datos variables y otros aspectos como prioridad. En los videojuegos y la IA de la máquina, controlas las señales y los datos con mucha precisión.
Nota 2 : Los modelos de dominio no tienen comportamiento polimórfico, ni tienen dependencias externas. Están "aislados". Eso no significa que tengan que ser 100% anémicos. Pueden tener mucha lógica relacionada con su construcción y alteración de propiedades mutables, si corresponde. Ver DDD "Objetos de valor" y entidades de Eric Evans y Mark Seemann.
Nota 3 : Linq y Lambda son muy comunes. Pero cuando un usuario crea una nueva función, rara vez usa Func o Callable como parámetros, mientras que en FP sería extraño ver una aplicación sin funciones que sigan ese patrón.
Nota 4 : No confundir el polimorfismo con la herencia. Un CatModel podría heredar AnimalBase para determinar qué propiedades tiene típicamente un animal. Pero como muestro, Modelos como este son un código de olor . Si ve este patrón, podría considerar desglosarlo y convertirlo en datos.
Nota 5 : Las funciones puras pueden (y lo hacen) aceptar funciones como parámetros. La función entrante puede ser impura, pero puede ser pura. Para fines de prueba, siempre sería puro. Pero en la producción, aunque se trata como puro, puede contener efectos secundarios. Eso no cambia el hecho de que la función pura es pura. Aunque la función del parámetro puede ser impura. No es confuso! :RE
fuente
Podrías hacer algo como esto ... php
fuente
$whostosay
convierte en el tipo de objeto que determina lo que se ejecuta. Lo anterior se puede modificar para aceptar adicionalmente otro parámetro$whattosay
para que un tipo que lo admite (por ejemplo'human'
) pueda utilizarlo.