La programación procesal / funcional no es de ninguna manera más débil que OOP , incluso sin entrar en argumentos de Turing (mi lenguaje tiene poder de Turing y puede hacer cualquier cosa que otro haga), lo que no significa mucho. En realidad, las técnicas orientadas a objetos se experimentaron por primera vez en lenguajes que no las tenían incorporadas. En este sentido, la programación OO es solo un estilo específico de programación de procedimientos . Pero ayuda a aplicar disciplinas específicas, como la modularidad, la abstracción y el ocultamiento de información que son esenciales para la comprensión y el mantenimiento del programa.
Algunos paradigmas de programación evolucionan desde la visión teórica de la computación. Un lenguaje como Lisp evolucionó a partir del cálculo lambda y la idea de la meta-circularidad de los lenguajes (similar a la reflexividad en el lenguaje natural). Las cláusulas de Horn engendraron Prolog y la programación de restricciones. La familia Algol también se debe al cálculo lambda, pero sin reflexividad incorporada.
Lisp es un ejemplo interesante, ya que ha sido el banco de pruebas de muchas innovaciones de lenguaje de programación, que se puede rastrear hasta su doble herencia genética.
Sin embargo, los idiomas evolucionan, a menudo bajo nuevos nombres. Un factor importante de la evolución es la práctica de programación. Los usuarios identifican las prácticas de programación que mejoran las propiedades de los programas, como la legibilidad, la mantenibilidad y la capacidad de prueba de la corrección. Luego, intentan agregar a los idiomas características o restricciones que admitirán y, a veces, impondrán estas prácticas para mejorar la calidad de los programas.
Lo que esto significa es que estas prácticas ya son posibles en un lenguaje de programación anterior, pero requiere comprensión y disciplina para usarlas. Incorporarlos a nuevos lenguajes como conceptos primarios con sintaxis específica hace que estas prácticas sean más fáciles de usar y de entender fácilmente, particularmente para los usuarios menos sofisticados (es decir, la gran mayoría). También hace la vida un poco más fácil para los usuarios sofisticados.
De alguna manera, es para diseñar el lenguaje lo que un subprograma / función / procedimiento es para un programa. Una vez que se identifica el concepto útil, se le da un nombre (posiblemente) y una sintaxis, de modo que pueda usarse fácilmente en todos los programas desarrollados con ese lenguaje. Y cuando tenga éxito, también se incorporará en futuros idiomas.
Ejemplo: recreación de la orientación a objetos
Ahora trato de ilustrar eso en un ejemplo (que sin duda podría pulirse aún más, con el tiempo). El propósito del ejemplo no es mostrar que un programa orientado a objetos puede escribirse en un estilo de programación procesal, posiblemente a expensas de la lisibilidad y la facilidad de mantenimiento. Prefiero tratar de mostrar que algunos lenguajes sin instalaciones OO pueden usar funciones de orden superior y estructura de datos para crear los medios para imitar efectivamente la Orientación de Objetos , para beneficiarse de sus cualidades con respecto a la organización del programa, incluyendo modularidad, abstracción y ocultación de información. .
Como dije, Lisp fue el banco de pruebas de mucha evolución del lenguaje, incluido el paradigma OO (aunque lo que podría considerarse el primer lenguaje OO fue Simula 67, en la familia Algol). Lisp es muy simple, y el código para su intérprete básico es menos de una página. Pero puedes hacer programación OO en Lisp. Todo lo que necesitas es funciones de orden superior.
No utilizaré la sintaxis esotérica de Lisp, sino más bien el seudocódigo, para simplificar la presentación. Y consideraré un problema esencial simple: ocultación de información y modularidad . Definir una clase de objetos mientras se evita que el usuario acceda (la mayoría de) la implementación.
Supongamos que quiero crear una clase llamada vector, que represente vectores bidimensionales, con métodos que incluyen: suma de vectores, tamaño de vector y paralelismo.
function vectorrec () {
function createrec(x,y) { return [x,y] }
function xcoordrec(v) { return v[0] }
function ycoordrec(v) { return v[1] }
function plusrec (u,v) { return [u[0]+v[0], u[1]+v[1]] }
function sizerec(v) { return sqrt(v[0]*v[0]+v[1]*v[1]) }
function parallelrec(u,v) { return u[0]*v[1]==u[1]*v[0]] }
return [createrec, xcoordrec, ycoordrec, plusrec, sizerec, parallelrec]
}
Entonces puedo asignar el vector creado a los nombres de funciones reales que se utilizarán.
[vector, xcoord, ycoord, vplus, vsize, vparallel] = vectorclass ()
¿Por qué ser tan complicado? Porque puedo definir en la función construcciones intermedias vectorrec que no quiero que sean visibles para el resto del programa, a fin de preservar la modularidad.
Podemos hacer otra colección en coordenadas polares.
function vectorpol () {
...
function pluspol (u,v) { ... }
function sizepol (v) { return v[0] }
...
return [createpol, xcoordpol, ycoordpol, pluspol, sizepol, parallelpol]
}
Pero es posible que desee utilizar indistintamente ambas implementaciones. Una forma de hacerlo es agregar un componente de tipo a todos los valores y definir todas las funciones anteriores en el mismo entorno: luego puedo definir cada una de las funciones devueltas para que primero pruebe el tipo de coordenadas, luego aplique la función específica para ello.
function vector () {
...
function plusrec (u,v) { ... }
...
function pluspol (u,v) { ... }
...
function plus (u,v) { if u[2]='rec' and v[2]='rec'
then return plusrec (u,v) ... }
return [ ..., plus, ...]
}
Lo que he ganado: las funciones específicas permanecen invisibles (debido al alcance de los identificadores locales), y el resto del programa solo puede usar las funciones más abstractas devueltas por la llamada a vectorclass.
Una objeción es que podría definir directamente cada una de las funciones abstractas en el programa y dejar dentro de la definición de las funciones dependientes del tipo de coordenadas. Entonces estaría oculto también. Eso es cierto, pero luego el código para cada tipo de coordenadas se cortaría en pequeños trozos repartidos por el programa, lo que es menos legible y mantenible.
En realidad, ni siquiera necesito darles un nombre, y podría mantener los valores funcionales como anónimos en una estructura de datos indexada por el tipo y una cadena que representa el nombre de la función. Esta estructura que es local para el vector de funciones sería invisible para el resto del programa.
Para simplificar el uso, en lugar de devolver una lista de funciones, puedo devolver una sola función llamada aplicar tomando como argumento un valor de tipo explícito y una cadena, y aplicar la función con el tipo y nombre adecuados. Esto se parece mucho a llamar a un método para una clase OO.
Me detendré aquí, en esta reconstrucción de una instalación orientada a objetos.
Lo que intenté hacer es mostrar que no es demasiado difícil construir una orientación de objeto utilizable en un lenguaje suficientemente potente, incluida la herencia y otras características similares. La metacircularidad del intérprete puede ayudar, pero principalmente en un nivel sintáctico, que aún está lejos de ser insignificante.
Los primeros usuarios de la orientación a objetos experimentaron los conceptos de esa manera. Y eso es generalmente cierto para muchas mejoras en los lenguajes de programación. Por supuesto, el análisis teórico también tiene un papel y ayudó a comprender o refinar estos conceptos.
Pero la idea de que los lenguajes que no tienen características OO están condenados al fracaso en algunos proyectos es simplemente injustificada. Si es necesario, pueden imitar la implementación de estas características de manera bastante efectiva. Muchos lenguajes tienen el poder sintáctico y semántico para orientar los objetos de manera bastante efectiva, incluso cuando no está integrado. Y eso es más que un argumento de Turing.
OOP no aborda las limitaciones de otros lenguajes, pero admite o impone metodologías de programación que ayudan a escribir un mejor programa, ayudando así a los usuarios menos experimentados a seguir las buenas prácticas que los programadores más avanzados han estado utilizando y desarrollando sin ese soporte.
Creo que un buen libro para entender todo esto podría ser Abelson & Sussman: estructura e interpretación de programas de computadora .