Historia olvidada de OOP

La mayoría de los paradigmas de programación que usamos hoy se estudiaron matemáticamente por primera vez en la década de 1930 utilizando las ideas del cálculo lambda y la máquina de Turing, que son variantes del modelo de computación universal (estos son sistemas formalizados que pueden realizar cálculos de propósito general). La tesis de Church-Turing mostró que el cálculo lambda y las máquinas de Turing son funcionalmente equivalentes. Es decir, estamos hablando del hecho de que todo lo que se puede calcular usando una máquina de Turing también se puede calcular usando el cálculo lambda, y viceversa.



Existe una idea errónea de que las máquinas de Turing pueden calcular todo lo que se puede calcular. Hay clases de problemas (por ejemplo, el problema de la detención ) que pueden calcularse utilizando máquinas de Turing solo en algunos casos. Cuando la palabra "computablemente" se usa en este texto, significa "computablemente por una máquina de Turing".

El cálculo de Lambda demuestra el enfoque de aplicar funciones a los cálculos de arriba hacia abajo. Una máquina de cinta Turing es un enfoque imperativo (paso a paso) para la informática, implementado de abajo hacia arriba.

Los lenguajes de programación de bajo nivel, como el código de máquina o el ensamblador, aparecieron en la década de 1940 y, a fines de la década de 1950, surgieron los primeros lenguajes populares de alto nivel que implementaron enfoques funcionales e imperativos. Entonces, los dialectos del lenguaje Lisp todavía se usan ampliamente, entre ellos Clojure, Scheme, AutoLisp, etc. En los años cincuenta aparecieron idiomas como FORTRAN y COBOL. Son ejemplos de lenguajes imperativos de alto nivel que aún están vivos. Aunque debe tenerse en cuenta que los lenguajes de la familia C, en la mayoría de las áreas, reemplazaron COBOL y FORTRAN.

Las raíces de la programación imperativa y funcional se encuentran en las matemáticas formales de la informática, aparecieron antes que las computadoras digitales. La Programación Orientada a Objetos (OOP), llegó más tarde, se origina en la revolución de la programación estructural, que tuvo lugar en los años sesenta y setenta del siglo pasado.

El primer objeto que conocí fue utilizado por Ivan Sutherland en su fatídica aplicación Sketchpad, creada entre 1961 y 1962, que describió en este trabajo en 1963. Los objetos eran caracteres gráficos que se mostraban en una pantalla de osciloscopio (tal vez este es el primer caso en la historia del uso de un monitor gráfico de computadora) y apoyaban la herencia a través de delegados dinámicos, que Ivan Sutherland llamó "maestros" en su trabajo. Cualquier objeto podría convertirse en un objeto maestro, las instancias adicionales del objeto se denominaron "ocurrencias". Esto convirtió al sistema Sketchpad en el propietario del primero de los famosos lenguajes de programación que implementaron la herencia del prototipo.

El primer lenguaje de programación, comúnmente conocido como "orientado a objetos", fue el lenguaje Simula, cuyas especificaciones se desarrollaron en 1965. Al igual que Sketchpad, Silmula proporcionó para trabajar con objetos, pero también incluyó clases, herencia basada en clases, subclases y métodos virtuales.

Un método virtual es un método definido en una clase que está diseñada para ser redefinida por subclases. Los métodos virtuales permiten que los programas invoquen métodos que pueden no existir en el momento en que se compila el código, mediante el envío dinámico para determinar qué método en particular se debe llamar durante la ejecución del programa. JavaScript tiene tipos dinámicos y utiliza una cadena de delegación para determinar qué método invocar. Como resultado, este lenguaje no necesita introducir el concepto de métodos virtuales a los programadores. En otras palabras, todos los métodos en JavaScript usan despacho en tiempo de ejecución, como resultado, los métodos en JavaScript no necesitan ser declarados "virtuales" para admitir esta función.

Opinión del padre de OOP sobre OOP


"Acuñé el término" orientado a objetos "y puedo decir que no quise decir C ++". Alan Kay, Conferencia OOPSLA, 1997.

Alan Kay acuñó el término "programación orientada a objetos", refiriéndose al lenguaje de programación Smalltalk (1972). Este lenguaje fue desarrollado por Alan Kay, Dan Ingles y otros empleados del Centro de Investigación Xerox PARC como parte del proyecto del dispositivo Dynabook. Smalltalk estaba más orientado a objetos que Simula. En Smalltalk, todo es un objeto, incluidas clases, enteros y bloques (cierres). La implementación inicial del lenguaje, Smalltalk-72, no tenía la capacidad de subclase. Esta característica apareció en Smalltalk-76.

Si bien Smalltalk apoyó clases y, como resultado, subclases, Smalltalk no puso estas ideas a la vanguardia. Era un lenguaje funcional que Lisp influyó tanto como Simula. Según Alan Kay, tratar las clases como un mecanismo de reutilización de código es un error. La industria de la programación presta gran atención a la creación de subclases, lo que distrae las ventajas reales de la programación orientada a objetos.

JavaScript y Smalltalk tienen mucho en común. Yo diría que JavaScript es la venganza de Smalltalk en el mundo por malinterpretar los conceptos de OOP. Ambos idiomas admiten las siguientes características:

  • Objetos
  • Funciones y cierres de primera clase.
  • Tipos dinámicos
  • Enlace tardío (las funciones y métodos se pueden reemplazar durante la ejecución del programa).
  • OOP sin un sistema de herencia basado en clases.

"Lamento haber inventado el término" objetos "para este fenómeno hace mucho tiempo, ya que su uso lleva al hecho de que muchas personas le dan una importancia primordial a una idea que no es tan importante como la principal. La idea principal es la mensajería ". Alan Kay

En una correspondencia por correo electrónico de 2003, Alan Kay aclaró lo que tenía en mente cuando llamó a Smalltalk "un lenguaje orientado a objetos".

"Para mí, OOP solo significa mensajería, almacenamiento local y protección, y estado de ocultación, y enlace muy tardío". Alan Kay

En otras palabras, de acuerdo con las ideas de Alan Kay, los ingredientes OOP más importantes son los siguientes:

  • Mensajería
  • Encapsulación
  • Enlace dinámico.

Es importante tener en cuenta que Alan Kay, el hombre que inventó el término "OOP" y lo trajo a las masas, no consideró que la herencia y el polimorfismo fueran los componentes más importantes de OOP.

La esencia de OOP


La combinación de mensajería y encapsulación sirve para varios propósitos importantes:

  • Evitar el estado mutable compartido de un objeto encapsulando el estado y aislando otros objetos de los cambios locales en su estado. La única forma de influir en el estado de otro objeto es pedirle (en lugar de darle una orden) que cambie enviándole un mensaje. Los cambios de estado se controlan a nivel local, celular, el estado no está disponible para otros objetos.
  • Separación de objetos entre sí. El remitente del mensaje se acopla libremente al destinatario a través de la API de mensajería.
  • Adaptabilidad y resistencia a los cambios durante la ejecución del programa a través del enlace tardío. La adaptación a los cambios durante la ejecución del programa ofrece muchas ventajas significativas, que Alan Kay considera muy importantes para la POO.

Alan Kay, quien expresó estas ideas, se inspiró en su conocimiento de la biología y lo que sabía sobre ARPANET (esta es una versión temprana de Internet). Es decir, estamos hablando de células biológicas y de computadoras individuales conectadas a la red. Incluso entonces, Alan Kay imaginó cómo los programas se ejecutan en grandes computadoras distribuidas (Internet), mientras que las computadoras individuales actúan como células biológicas, trabajando independientemente con su propio estado aislado e intercambiando datos con otras computadoras mediante el envío de mensajes.

"Me di cuenta de que una metáfora para una célula o computadora ayudará a eliminar los datos [...]". Alan Kay

Al decir "ayudar a deshacerse de los datos", Alan Kay, por supuesto, era consciente de los problemas causados ​​por el estado mutable compartido y la fuerte conectividad causada por el intercambio de datos. Hoy, estos temas son ampliamente escuchados. Pero a fines de la década de 1960, los programadores de ARPANET no estaban contentos con la necesidad de elegir una representación de modelo de datos para sus programas antes de desarrollar programas. Los desarrolladores querían alejarse de esta práctica, ya que, al haberse introducido en el marco definido por la presentación de los datos, es más difícil cambiar algo en el futuro.

El problema era que se necesitaban diferentes formas de presentar los datos, para acceder a ellos, diferentes códigos y diferentes sintaxis en los lenguajes de programación utilizados en algún momento. El Santo Grial aquí sería una forma universal de acceder y administrar datos. Si todos los datos tuvieran el mismo aspecto para el programa, esto resolvería muchos problemas de los desarrolladores con respecto al desarrollo y mantenimiento de los programas.
Alan Kay intentó "deshacerse" de la idea, según la cual los datos y los programas eran, en cierto sentido, entidades independientes. No se consideran como tales en List o Smalltalk. No hay separación entre lo que se puede hacer con datos (con valores, variables, estructuras de datos, etc.) y construcciones de software como funciones. Las funciones son "ciudadanos de primera clase" y los programas pueden cambiar durante su ejecución. En otras palabras, Smalltalk no tiene una relación especial y privilegiada con los datos.

Alan Kay, además, consideraba los objetos como estructuras algebraicas, lo que daba garantías definidas y matemáticamente comprobables de su comportamiento.

"Mi formación matemática me permitió comprender que cada objeto puede tener varios modelos algebraicos asociados, que puede haber grupos enteros de modelos similares y que pueden ser muy, muy útiles". Alan Kay

Se demostró que es así, y esto formó la base para objetos, como promesas y lentes, además, la teoría de la categoría fue influenciada por ambos.
La naturaleza algebraica de cómo Alan Kay vio los objetos permitiría a los objetos proporcionar verificación formal, comportamiento determinista y mejorar la capacidad de prueba, ya que los modelos algebraicos son, en esencia, operaciones que obedecen a varias reglas en forma de ecuaciones.

En la jerga de los programadores, los "modelos algebraicos" son abstracciones creadas a partir de funciones (operaciones) que van acompañadas de ciertas reglas, impuestas por pruebas unitarias que estas funciones deben aprobar (axiomas, ecuaciones).

Estas ideas se han olvidado durante décadas en la mayoría de los lenguajes orientados a objetos de la familia C, incluidos C ++, Java, C #, etc. Pero estas ideas comienzan la búsqueda del viaje de regreso, en versiones recientes de los lenguajes orientados a objetos más utilizados.

En esta ocasión, alguien puede decir que el mundo de la programación redescubre los beneficios de la programación funcional y proporciona argumentos racionales en el contexto de los lenguajes orientados a objetos.

Al igual que JavaScript y Smalltalk anteriormente, la mayoría de los lenguajes modernos orientados a objetos se están volviendo cada vez más "multi-paradigmáticos". No hay razón para elegir entre programación funcional y OOP. Cuando miramos la esencia histórica de cada uno de estos enfoques, se ven no solo como compatibles, sino también como ideas complementarias.

¿Cuál, de acuerdo con los pensamientos de Alan Kay, es lo más importante en la OLP?

  • Encapsulación
  • Mensajería
  • Enlace dinámico (la capacidad de los programas para desarrollarse y adaptarse a los cambios durante su ejecución).

¿Qué es insignificante en OOP?

  • Clases
  • Herencia basada en clases.
  • Relación particular con objetos, funciones o datos.
  • Palabra clave new .
  • Polimorfismo
  • Mecanografía estática.
  • Actitud hacia las clases como "tipos".

Si conoce Java o C #, podría pensar que la tipificación estática o el polimorfismo son los ingredientes más importantes de la POO, pero Alan Kay prefiere tratar con patrones de comportamiento universal en forma algebraica. Aquí hay un ejemplo escrito en Haskell:

 fmap :: (a -> b) -> fa -> fb 

Esta es la firma del functor de map universal, que funciona con los tipos indefinidos b , aplicando la función de a a b en el contexto del functor a para crear el functor b . "Functor" es una palabra de la jerga matemática, cuyo significado se reduce a "soporte de la operación de visualización". Si está familiarizado con el método [].map() en JavaScript, ya sabe lo que esto significa.

Aquí hay un par de ejemplos de JavaScript:

 // isEven = Number => Boolean const isEven = n => n % 2 === 0; const nums = [1, 2, 3, 4, 5, 6]; //  map   `a => b`    `a` ( `this`) //     `b` //    `a`   `Number`,   `b`  `Boolean` const results = nums.map(isEven); console.log(results); // [false, true, false, true, false, true] 

El método .map() es universal, en el sentido de que b pueden ser de cualquier tipo, y este método hace frente a una situación similar sin problemas, ya que las matrices son estructuras de datos que implementan las leyes algebraicas de los functores. Los tipos para .map() no importan, ya que este método no intenta trabajar directamente con los valores correspondientes. En cambio, utiliza una función que espera y devuelve valores de los tipos correspondientes que son correctos desde el punto de vista de la aplicación.

 // matches = a => Boolean //  `a`    ,   const matches = control => input => input === control; const strings = ['foo', 'bar', 'baz']; const results = strings.map(matches('bar')); console.log(results); // [false, true, false] 

La relación de tipos universales puede ser difícil de expresar correcta y completamente en lenguajes como TypeScript, pero es muy simple de hacer en el sistema de tipos Hindley-Milner utilizado en Haskell, que admite tipos superiores (tipos de tipos).

La mayoría de los sistemas de tipos imponen restricciones demasiado fuertes para permitir la libre expresión de ideas dinámicas y funcionales, como la composición de funciones, la composición libre de objetos, la expansión de objetos durante la ejecución del programa, el uso de combinadores, lentes, etc. En otras palabras? Los tipos estáticos a menudo dificultan la escritura de software utilizando métodos de compilación.

Si su sistema de tipos tiene demasiadas restricciones (como en TypeScript o Java), entonces, para lograr los mismos objetivos, debe escribir un código más complejo que cuando usa idiomas con un enfoque más libre para escribir. Esto no significa que el uso de tipos estáticos sea una idea desafortunada, o que todas las implementaciones de tipos estáticos tengan las mismas limitaciones. Por ejemplo, he encontrado muchos menos problemas al trabajar con el sistema de tipo Haskell.

Si eres fanático de los tipos estáticos y no estás en contra de las restricciones, te deseo siete pies debajo de la quilla. Pero si encuentra que algunas de las ideas expresadas aquí son difíciles de implementar porque no es fácil escribir funciones obtenidas al componer otras funciones y estructuras algebraicas compuestas, entonces culpe al sistema de tipos y no a la idea. A los conductores les gustan las comodidades que les brindan los SUV cuadro a cuadro, pero nadie se queja de que no vuelan. Para volar, necesita un vehículo que tenga más grados de libertad.

Si las restricciones simplifican su código, ¡excelente! Pero si las restricciones lo obligan a escribir código más complejo, entonces tal vez algo esté mal con estas restricciones.

¿Qué es un "objeto"?


La palabra "objeto", con el tiempo, ha adquirido muchas connotaciones secundarias de significado. Lo que llamamos "objetos" en JavaScript son simplemente tipos de datos compuestos, sin ningún indicio de la programación basada en la clase o las ideas de mensaje de Alan Kay.

En JavaScript, estos objetos pueden admitir, y a menudo admiten, la encapsulación, el paso de mensajes, la separación del comportamiento a través de métodos, incluso el polimorfismo utilizando subclases (aunque utilizando una cadena de delegación en lugar de un envío basado en tipos).

Alan Kay quería deshacerse de la diferencia entre el programa y sus datos. JavaScript, en cierta medida, logra este objetivo colocando los métodos de objeto en el mismo lugar que las propiedades que almacenan los datos. A cualquier propiedad, por ejemplo, se le puede asignar cualquier función. Puede construir el comportamiento del objeto dinámicamente y cambiar el contenido semántico del objeto durante la ejecución del programa.

Un objeto es solo una estructura de datos compuesta, y no necesita nada especial para ser considerado un objeto. Sin embargo, la programación usando objetos no conduce al hecho de que dicho código resulta estar "orientado a objetos", así como el uso de funciones no hace que el código sea "funcional".

OOP ya no es una verdadera OOP


Dado que el concepto de "objeto" en los lenguajes de programación modernos significa mucho menos de lo que Alan Kay quiso decir, uso la palabra "componente" en lugar de la palabra "objeto" para describir las reglas de esta OOP. Muchos objetos son propiedad y control directo de algún código JavaScript de terceros, pero los componentes deben encapsular su propio estado y controlarlo.

Esto es lo que es la verdadera OOP:

  • Programación utilizando componentes (Alan Kay los llama "objetos").
  • El estado del componente debe estar encapsulado.
  • Para la comunicación entre entidades, se utiliza la mensajería.
  • Los componentes se pueden agregar, modificar y reemplazar en tiempo de ejecución.

La mayoría de los comportamientos de los objetos se pueden definir de manera universal utilizando estructuras de datos algebraicos. No hay necesidad de herencia. Los componentes pueden reutilizar comportamientos de funciones públicas y módulos de importación, sin tener que hacer públicos sus datos.

Manipular objetos en JavaScript o usar una herencia basada en clases no significa que alguien esté involucrado en la programación de OOP. Pero el uso de componentes de tal manera - significa. Pero es muy difícil deshacerse de las ideas establecidas sobre los términos, por lo que tal vez deberíamos dejar el término "OOP" y llamar a lo que los "componentes" anteriores se usan como "Programación Orientada a Mensajes (MOP)". Utilizaremos el término "MOP" a continuación para hablar sobre la programación orientada a mensajes.

Por casualidad, la palabra inglesa "trapeador" se traduce como "trapeador" y, como saben, se utilizan para restablecer el orden.

¿Cómo se ve un buen MOP?


La mayoría de los programas modernos tienen una determinada interfaz de usuario (Interfaz de usuario, UI) responsable de interactuar con el usuario, algún código dedicado a administrar el estado de la aplicación (datos del usuario) y un código que funciona con el sistema o es responsable del intercambio de datos con la red.

Para admitir el funcionamiento de cada uno de estos sistemas, pueden ser necesarios procesos de larga duración, como los oyentes de eventos. Aquí necesitará el estado de la aplicación: para almacenar información como las conexiones de red, el estado de las cosas con los controles de la interfaz y la aplicación en sí.

Un buen MOP significa que, en lugar de que todos esos sistemas tengan acceso al estado del otro y puedan controlarlos directamente, interactúan entre sí a través de mensajes. Cuando el usuario hace clic en el botón "SAVE" , se puede enviar el mensaje "SAVE" . El componente de la aplicación de gestión de estado puede interpretar este mensaje y redirigirlo al controlador responsable de la actualización desde el estado (como una función reductora pura). Quizás, después de actualizar el estado, el componente responsable de administrar el estado envía el mensaje "STATE_UPDATED" componente de interfaz de usuario, que, a su vez, interpreta el estado, decide qué partes de la interfaz deben actualizarse y transfiere el estado actualizado a los subcomponentes responsables de trabajar con Elementos de interfaz específicos.

Mientras tanto, el componente responsable de las conexiones de red puede monitorear la conexión del usuario a otra computadora en la red, escuchar mensajes y enviar una vista actualizada del estado para guardarlo en la máquina remota. Dicho componente es responsable de trabajar con mecanismos de red, sabe si la conexión funciona o no, y así sucesivamente.

Sistemas de aplicación similares no deben conocer los detalles de sus otras partes. Solo deberían preocuparse por resolver sus propios problemas. Los componentes del sistema pueden desmontarse y ensamblarse como constructor. Implementan interfaces estandarizadas, lo que significa que pueden interactuar entre sí. Mientras se cumplan los requisitos bien conocidos para la interfaz de los componentes, dichos componentes pueden ser reemplazados por otros, con las mismas interfaces, pero haciendo lo mismo de manera diferente, o realizando, recibiendo los mismos mensajes, algo completamente diferente. Puede cambiar un componente a otro incluso durante la ejecución del programa; esto no interrumpirá su trabajo.

Los componentes de un sistema de software ni siquiera tienen que estar en la misma computadora. El sistema puede ser descentralizado. El almacenamiento en red puede colocar datos en un sistema de almacenamiento descentralizado como IPFS , como resultado, el usuario es independiente del estado de una máquina en particular, lo que garantiza la seguridad de sus datos. Con este enfoque, los datos se almacenan de manera confiable y se protegen de intrusos.

La OLP, en parte, quedó bajo la influencia de las ideas de ARPANET, y uno de los objetivos de este proyecto era crear una red descentralizada que fuera resistente a ataques como un ataque nuclear.

Un buen sistema MOP puede caracterizarse por un nivel similar de estabilidad utilizando componentes que admiten el intercambio en caliente mientras la aplicación se está ejecutando. Podrá continuar funcionando si el usuario trabaja con él desde un teléfono celular y está fuera de la cobertura de la red debido a que ha entrado en el túnel. Si un huracán interrumpió el suministro de energía de uno de los centros de datos en los que se encuentran sus servidores, también continuará funcionando.

Es hora de que el mundo del software se libere de un exitoso experimento de herencia basado en clases y adopte los principios matemáticos y científicos que estuvieron a la vanguardia de la POO.

Es hora de que los desarrolladores creemos programas más flexibles, estables y hermosos utilizando una combinación armoniosa de MOP y programación funcional.
Por cierto, el acrónimo "MOP" ya está en uso, describiendo "Programación orientada al monitoreo", pero este concepto, a diferencia de OOP, simplemente desaparecerá silenciosamente.

Por lo tanto, no se desanime si el término "MOP" no parece una palabra de la jerga de los programadores. Simplemente ordena tu OOP con los principios de MOP anteriores.

Source: https://habr.com/ru/post/es428582/


All Articles