Omitir herencia en lenguajes de programación

10

Estoy desarrollando mi propio lenguaje de programación. Es un lenguaje de propósito general (piense en Python tipeado estáticamente para el escritorio, es decir int x = 1;) no destinado a la nube.

¿Crees que está bien no permitir la herencia o Mixins? (dado que el usuario al menos tendría interfaces)

Por ejemplo: Google Go, un lenguaje de sistemas que conmocionó a la comunidad de programación al no permitir la herencia.

Christopher
fuente

Respuestas:

4

De hecho, encontramos cada vez más que el uso de la herencia es subóptimo porque (entre otros) conduce a un acoplamiento estrecho.

La herencia de implementación siempre se puede reemplazar por herencia de interfaz más composición, y los diseños de software más modernos tienden a usar la herencia cada vez menos, a favor de la composición.

Entonces , , no proporcionar herencia en particular es una decisión de diseño completamente válida y muy en boga .

Mixins, por el contrario, ni siquiera son (todavía) una característica del lenguaje corriente, y los idiomas que no proporcionan una función llamada “mixin” a menudo se entienden cosas muy diferentes por el mismo. Siéntase libre de proporcionarlo o no. Personalmente, lo encuentro muy útil, pero implementarlo correctamente puede ser muy difícil.

Konrad Rudolph
fuente
13

Razonar acerca de si la herencia (o cualquier característica individual, realmente) es necesaria o no sin considerar también el resto de la semántica del lenguaje no tiene sentido; Estás discutiendo en el vacío.

Lo que necesita es una filosofía de diseño de lenguaje consistente; El lenguaje debe ser capaz de resolver elegantemente los problemas para los que está diseñado. El modelo para lograr esto puede o no requerir herencia, pero es difícil juzgar esto sin el panorama general.

Si, por ejemplo, su lenguaje tiene funciones de primera clase, aplicación de funciones parciales, tipos de datos polimórficos, variables de tipo y tipos genéricos, prácticamente ha cubierto las mismas bases que tendría con la herencia clásica de OOP, pero utilizando un paradigma diferente.

Si tiene un enlace tardío, una tipificación dinámica, métodos como propiedades, argumentos de funciones flexibles y funciones de primera clase, también cubre los mismos motivos, pero nuevamente, utilizando un paradigma diferente.

(Encontrar ejemplos para los dos paradigmas descritos se deja como ejercicio para el lector).

Por lo tanto, piense en el tipo de semántica que desea, juegue con ellos y vea si son suficientes sin herencia. Si no lo están, puede decidir agregar la herencia a la mezcla o puede decidir que falta algo más.

tdammers
fuente
9

Si.

Creo que no está permitido permitir la herencia, especialmente si su idioma se escribe dinámicamente. Puede lograr una reutilización de código similar, por ejemplo, por delegación o composición. Por ejemplo, he escrito algunos programas moderadamente complicados, bueno, no tan complicados:) en JavaScript sin ninguna herencia. Básicamente utilicé objetos como estructuras de datos algebraicos con algunas funciones adjuntas como métodos. También tenía un montón de funciones que no eran métodos que operaran en estos objetos.

Si tiene una escritura dinámica, y supongo que sí, también puede tener polimorfismo sin herencia. Además, si permite agregar métodos arbitrarios a los objetos en tiempo de ejecución, realmente no necesitará mucho cosas como mixins.

Otra opción, que creo que es buena, es emular JavaScript y usar prototipos. Estos son más simples que las clases pero también muy flexibles. Solo algo a tener en cuenta.

Entonces, dicho todo, iría por ello.

Tikhon Jelvis
fuente
2
Mientras exista una forma razonable de realizar el tipo de tareas que normalmente se manejan con herencia, continúe. Es posible probar algunos casos de uso, para asegurarse de que (posiblemente incluso yendo tan lejos como para problemas de ejemplo solicitar a otros, para evitar la auto-sesgo ...)
comingstorm
No, está estática y fuertemente tipado, lo mencionaré en la parte superior.
Christopher
Como está estáticamente escrito, no puede simplemente agregar métodos aleatorios a los objetos en tiempo de ejecución. Sin embargo, todavía puede escribir pato: consulte los protocolos Gosu para ver un ejemplo. Usé protocolos un poco durante el verano y fueron realmente útiles. Gosu también tiene "mejoras" que son una forma de agregar métodos a las clases después del hecho. Podría considerar agregar algo así también.
Tikhon Jelvis
2
Por favor, por favor, por favor, deja de relacionar la herencia y la reutilización del código. Desde hace tiempo se sabe que la herencia es una herramienta realmente mala para la reutilización de código.
Jan Hudec
3
La herencia es solo una herramienta para la reutilización de código. A menudo es una muy buena herramienta, y otras veces es horrible. Preferir la composición sobre la herencia no significa nunca usar la herencia en absoluto.
Michael K
8

Sí, es una decisión de diseño perfectamente razonable omitir la herencia.

De hecho, hay muy buenas razones para eliminar la herencia de implementación, ya que puede producir un código extremadamente complejo y difícil de mantener. Incluso iría tan lejos como para considerar la herencia (ya que generalmente se implementa en la mayoría de los lenguajes OOP) como un error.

Clojure, por ejemplo, no proporciona herencia de implementación, prefiriendo proporcionar un conjunto de características ortogonales (protocolos, datos, funciones, macros) que se pueden usar para lograr los mismos resultados, pero mucho más limpiamente.

Aquí hay un video que encontré muy esclarecedor sobre este tema general, donde Rich Hickey identifica las fuentes fundamentales de complejidad en los lenguajes de programación (incluida la herencia) y presenta alternativas para cada uno: simple hecho fácil

mikera
fuente
En muchos casos, la herencia de la interfaz es más apropiada. Pero si se usa con moderación, la herencia de implementación puede permitir la eliminación de una gran cantidad de código repetitivo, y es la solución más elegante. Solo requiere programadores que sean más inteligentes y más cuidadosos que el mono Python / JavaScript promedio.
Erik Alapää
4

Cuando me topé por primera vez con el hecho de que las clases VB6 no admiten herencia (solo interfaces), realmente me molestó (y aún lo hace).

Sin embargo, la razón por la que es tan malo es que tampoco tenía parámetros de constructor, por lo que no podía hacer una inyección de dependencia (DI) normal. Si tiene DI, entonces eso es más importante que la herencia porque puede seguir el principio de favorecer la composición sobre la herencia. Esa es una mejor manera de reutilizar el código de todos modos.

Sin embargo, ¿no tienes Mixins? Si desea implementar una interfaz y delegar todo el trabajo de esa interfaz a un conjunto de objetos mediante inyección de dependencia, entonces los Mixins son ideales. De lo contrario, debe escribir todo el código repetitivo para delegar todos los métodos y / o propiedades al objeto secundario. Lo hago mucho (gracias a C #) y es una cosa que desearía no tener que hacer.

Scott Whitlock
fuente
Sí, lo que provocó esta pregunta es la implementación del SVG DOM donde la reutilización del código es muy beneficiosa.
Christopher
2

Para no estar de acuerdo con otra respuesta: no, descarta funciones cuando son incompatibles con algo que desea más. Java (y otros lenguajes GC'd) descartó la administración de memoria explícita porque quería más seguridad de tipo. Haskell rechazó la mutación porque quería más razonamiento equitativo y tipos elegantes. Incluso C descartó (o declaró ilegal) ciertos tipos de alias y otros comportamientos porque quería más optimizaciones del compilador.

Entonces la pregunta es: ¿Qué quieres más que herencia?

Ryan Culpepper
fuente
1
Solo que la gestión explícita de la memoria y la seguridad de tipo no tienen relación alguna y definitivamente no son incompatibles. Lo mismo se aplica a la mutación y los "tipos elegantes". Estoy de acuerdo con el razonamiento general, pero sus ejemplos están bastante mal elegidos.
Konrad Rudolph
@KonradRudolph, la gestión de memoria y la seguridad de tipos están estrechamente relacionadas. Una operación free/ deletele da la capacidad de invalidar referencias; a menos que su sistema de tipos pueda rastrear todas las referencias afectadas, eso hace que el idioma sea inseguro. En particular, ni C ni C ++ son de tipo seguro. Es cierto que puede hacer compromisos en uno u otro (por ejemplo, tipos lineales o restricciones de asignación) para que estén de acuerdo. Para ser precisos, debería haber dicho que Java quería seguridad de tipos con un sistema de tipos particular y simple y una asignación sin restricciones más.
Ryan Culpepper
Bueno, eso depende de cómo defina la seguridad de tipo. Por ejemplo, si toma la definición (totalmente razonable) de seguridad de tipos en tiempo de compilación y desea que la integridad de referencia sea parte de su sistema de tipos, Java tampoco es seguro para los tipos (ya que permite nullreferencias). Prohibir freees una restricción adicional bastante arbitraria. En resumen, si un idioma es de tipo seguro depende más de su definición de seguridad de tipo que del idioma.
Konrad Rudolph el
1
@ Ryan: los punteros son perfectamente seguros para escribir. Siempre se comportan de acuerdo con su definición. Puede que no sea como te gustan, pero siempre depende de cómo estén definidos. Estás tratando de estirarlos para que sean algo que no son. Los punteros inteligentes pueden garantizar la seguridad de la memoria de manera bastante trivial en C ++.
DeadMG
@KonradRudolph: en Java, cada Treferencia se refiere a uno nullo un objeto de una clase que se extiende T; nulles feo, pero las operaciones en nullarrojar una excepción bien definida en lugar de corromper el tipo invariante anterior. Contraste con C ++: después de una llamada a delete, un T*puntero puede apuntar a la memoria que ya no contiene un Tobjeto. Peor aún, al hacer una asignación de campo utilizando ese puntero en una asignación, puede actualizar un campo de un objeto de una clase diferente por completo si se coloca en una dirección cercana. Eso no es seguridad de tipo según cualquier definición útil del término.
Ryan Culpepper
1

No.

Si desea eliminar una característica del lenguaje base, entonces está declarando universalmente que nunca, nunca es necesario (o injustificadamente difícil de implementar, lo que no se aplica aquí). Y "nunca" es una palabra fuerte en ingeniería de software. Necesitaría una justificación muy poderosa para hacer tal declaración.

DeadMG
fuente
8
Eso no es cierto: todo el diseño de Java consistía en eliminar características como la sobrecarga del operador, la herencia múltiple y la gestión manual de la memoria. Además, creo que tratar cualquier característica del lenguaje como "sagrado" está mal; en lugar de justificar la eliminación de una función, debe justificar su adición ; no se me ocurre ninguna función que deba tener cada idioma.
Tikhon Jelvis
66
@TikhonJelvis: Por eso, Java es un lenguaje terrible y no tiene nada más que "USAR LA HERENCIA RECOGIDA EN BASURA" es la razón número uno . Las características del lenguaje deberían estar justificadas, estoy de acuerdo, pero esta es una característica del lenguaje base: tiene muchas aplicaciones útiles y el programador no puede replicarla sin violar DRY.
DeadMG
1
@DeadMG wow! Lenguaje bastante fuerte.
Christopher
@DeadMG: comencé a escribir una refutación como un comentario, pero luego la convertí en una respuesta (qv).
Ryan Culpepper
1
"La perfección se logra no cuando no queda nada más que agregar, sino cuando no queda nada por eliminar" (q)
9000
1

Hablando desde una perspectiva estrictamente C ++, la herencia es útil para dos propósitos principales:

  1. Permite la resusabilidad del código
  2. En combinación con la anulación de una clase secundaria, le permite usar un puntero de clase base para manejar un objeto de clase secundaria sin conocer su tipo.

Para el punto 1, siempre y cuando tenga alguna forma de compartir código sin tener que disfrutar de demasiadas acrobacias, puede eliminar la herencia. Para el punto 2. Puede seguir el camino de Java e insistir en una interfaz para implementar esta característica.

Los beneficios de eliminar la herencia son

  1. evitar largas jerarquías y sus problemas asociados. Su mecanismo de código compartido debería ser lo suficientemente bueno para eso.
  2. Evite que los cambios en las clases para padres rompan las clases para niños.

La compensación es en gran medida entre "No te repitas" y Flexibilidad por un lado y evitar problemas en el otro lado. Personalmente, odiaría ver que la herencia pasara de C ++ simplemente porque algún otro desarrollador puede no ser lo suficientemente inteligente como para prever los problemas.

DPD
fuente
1

¿Crees que está bien no permitir la herencia o Mixins? (dado que el usuario al menos tendría interfaces)

Puede hacer mucho trabajo muy útil sin herencia de implementación o mixins, pero me pregunto si debería tener algún tipo de herencia de interfaz, es decir, una declaración que diga si un objeto implementa la interfaz A, entonces también necesita la interfaz B (es decir, A es una especialización de B, por lo que hay una relación de tipo). Por otro lado, su objeto resultante solo necesita registrar que implementa ambas interfaces, por lo que no hay demasiada complejidad allí. Todo perfectamente factible.

Sin embargo, la falta de herencia de implementación tiene una desventaja clara: no podrá construir vtables indexadas numéricamente para sus clases y, por lo tanto, tendrá que hacer búsquedas hash para cada llamada a método (o descubrir una forma inteligente de evitarlas). Esto podría ser doloroso si está enrutando incluso valores fundamentales (por ejemplo, números) a través de este mecanismo. ¡Incluso una muy buena implementación de hash puede ser costosa cuando la golpeas varias veces en cada ciclo interno!

Compañeros de Donal
fuente