triciclicos studio programacion nombres móviles intoxicacion desarrollo curso comerciales aplicaciones antidepresivos java oop programming-languages haskell functional-programming

java - studio - manual de programacion android pdf



¿Por qué los ADT son buenos y la herencia es mala? (4)

Soy programador OO desde hace mucho tiempo y un novato en programación funcional. Desde mi pequeña exposición, los tipos de datos algebraicos solo se parecen a un caso especial de herencia en el que solo tiene un nivel de jerarquía y la superclase no se puede extender fuera del módulo.

Está describiendo tipos de sumas cerradas, la forma más común de tipos de datos algebraicos, como se ve en F # y Haskell. Básicamente, todos están de acuerdo en que son una característica útil para tener en el sistema de tipos, principalmente porque la coincidencia de patrones facilita su disección por la forma y el contenido, y también porque permiten la verificación de exhaustividad y redundancia.

Sin embargo, hay otras formas de tipos de datos algebraicos. Una limitación importante de la forma convencional es que están cerradas, lo que significa que un tipo de suma cerrada previamente definido no puede extenderse con nuevos constructores de tipo (parte de un problema más general conocido como "el problema de la expresión"). Las variantes polimórficas de OCaml permiten tanto tipos de sumas abiertas como cerradas y, en particular, la inferencia de los tipos de sumas. En contraste, Haskell y F # no pueden inferir tipos de suma. Las variantes polimórficas resuelven el problema de expresión y son extremadamente útiles. De hecho, algunos lenguajes están construidos completamente en tipos de datos algebraicos extensibles en lugar de tipos de suma cerrada.

En el extremo, también tienes idiomas como Mathematica donde "todo es una expresión". Por lo tanto, el único tipo en el sistema de tipos forma un álgebra trivial "singleton". Esto es "extensible" en el sentido de que es infinito y, nuevamente, culmina en un estilo de programación completamente diferente.

Así que mi pregunta (potencialmente tonta) es: si los TDA son solo eso, un caso especial de herencia (de nuevo, esta suposición puede ser incorrecta; corríjame en ese caso), entonces, ¿por qué la herencia recibe todas las críticas y los TDA reciben todos los elogios? ?

Creo que se está refiriendo específicamente a la herencia de implementación (es decir, al anular la funcionalidad de una clase padre) en lugar de a la herencia de interfaz (es decir, implementar una interfaz consistente). Esta es una distinción importante. La herencia de implementación a menudo se odia mientras que la herencia de interfaz a menudo se ama (por ejemplo, en F #, que tiene una forma limitada de ADT).

Realmente quieres tanto ADT como herencia de interfaz. Idiomas como OCaml y F # ofrecen ambos.

Soy programador OO desde hace mucho tiempo y un novato en programación funcional. Desde mi pequeña exposición, los tipos de datos algebraicos solo se parecen a un caso especial de herencia en el que solo tiene un nivel de jerarquía y la superclase no se puede extender fuera del módulo.

Así que mi pregunta (potencialmente tonta) es: si los TDA son solo eso, un caso especial de herencia (de nuevo, esta suposición puede ser incorrecta; corríjame en ese caso), entonces, ¿por qué la herencia recibe todas las críticas y los TDA reciben todos los elogios? ?

Gracias.


Creo que los TDA son complementarios a la herencia. Ambos te permiten crear código extensible, pero la forma en que funciona la extensibilidad es diferente:

  • Los ADT facilitan la adición de nuevas funciones para trabajar con los tipos existentes
    • Puede agregar fácilmente una nueva función que funcione con ADT, que tiene un conjunto fijo de casos
    • Por otro lado, agregar un nuevo caso requiere modificar todas las funciones
  • La herencia facilita la adición de nuevos tipos cuando tiene una funcionalidad fija
    • Puede crear fácilmente la clase heredada e implementar un conjunto fijo de funciones virtuales
    • Por otro lado, agregar una nueva función virtual requiere modificar todas las clases heredadas

Tanto el mundo orientado a objetos como el mundo funcional desarrollaron sus formas de permitir el otro tipo de extensibilidad. En Haskell, puede usar clases de tipos, en ML / OCaml, la gente usaría el diccionario de funciones o quizás (?) Functores para obtener la extensibilidad del estilo de herencia . Por otro lado, en OOP, las personas usan el patrón de visitante, que es esencialmente una forma de obtener algo como ADT.

Los patrones de programación habituales son diferentes en OOP y FP, por lo tanto, cuando está programando en un lenguaje funcional, está escribiendo el código de una manera que requiere la extensibilidad del estilo funcional con mayor frecuencia (y de manera similar en OOP). En la práctica, creo que es genial tener un lenguaje que te permita usar ambos estilos dependiendo del problema que estés tratando de resolver.


En mi experiencia, lo que la gente suele considerar "mala" acerca de la herencia implementada por la mayoría de los lenguajes OO no es la idea de la herencia en sí, sino la idea de que las subclases modifican el comportamiento de los métodos definidos en la superclase (método que prevalece), específicamente en presencia Estado mutable . Es realmente la última parte que es el kicker. La mayoría de los lenguajes OO tratan los objetos como "estado de encapsulación", lo que equivale a permitir una mutación desenfrenada de estado dentro de los objetos. Por lo tanto, surgen problemas cuando, por ejemplo, una superclase espera que un determinado método modifique una variable privada, pero una subclase reemplaza al método para hacer algo completamente diferente. Esto puede introducir errores sutiles que el compilador no puede impedir.

Tenga en cuenta que en la implementación de Haskell del polimorfismo de subclase, el estado mutable no está permitido, por lo que no tiene tales problemas.

Además, vea esta objeción al concepto de subtipo.


Tomas Petricek tiene los fundamentos exactamente correctos; es posible que también desee ver los escritos de Phil Wadler sobre el "problema de expresión".

Hay otras dos razones por las que algunos de nosotros preferimos tipos de datos algebraicos a la herencia:

  • Utilizando tipos de datos algebraicos, el compilador puede (y lo hace) decirle si ha olvidado un caso o si un caso es redundante. Esta habilidad es especialmente útil cuando hay muchas más operaciones en las cosas que tipos de cosas . (Por ejemplo, muchas más funciones que tipos de datos algebraicos, o muchos más métodos que constructores OO). En un lenguaje orientado a objetos, si deja un método fuera de una subclase, el compilador no puede decir si es un error o si fue su intención. para heredar el método de superclase sin cambios.

  • Este es más subjetivo: muchas personas han notado que si la herencia se usa de manera adecuada y agresiva, la implementación de un algoritmo se puede difuminar fácilmente en media docena de clases, e incluso con un buen navegador de clases puede ser difícil seguir el Lógica del programa (flujo de datos y flujo de control). Sin un navegador de clase agradable, no tienes oportunidad. Si desea ver un buen ejemplo, intente implementar bignums en Smalltalk, con conmutación por error automática a bignums en el desbordamiento. Es una gran abstracción, pero el lenguaje hace que la implementación sea difícil de seguir. Usando funciones en tipos de datos algebraicos, la lógica de su algoritmo generalmente está en un solo lugar, o si se divide, se divide en funciones que tienen contratos que son fáciles de entender.

PS ¿Qué estás leyendo? No conozco a ninguna persona responsable que diga "ADTs good; OO bad".