Cómo forzar llamar solo los métodos de superclase a pesar de que han sido anulados (en Ocaml)
-
26-10-2019 - |
Pregunta
Para ser honesto, no sé mucho sobre el sistema de objetos de Ocaml. El siguiente fragmento ilustra mi problema:
class foo =
object (o)
method foo1 y =
print_endline "foo1";
o#foo2 (y - 1)
method foo2 y =
print_endline "foo2";
if y > 0 then o#foo2 y else print_endline "end"
end
class bar =
object (o : 'self_type)
inherit foo as super
method foo2 y =
print_endline "bar2";
if y > 0 then super#foo1 y else print_endline "endbar"
end
let _ = (new bar)#foo2 3
Cuando se ejecuta, el fragmento produce la siguiente salida:
bar2
foo1
bar2
foo1
bar2
endbar
, mostrando que el método de superclase FOO1 (llamado Via Super#foo1) ejecuta el método de anulación FOO2 desde la subclase. En su lugar, hubiera esperado que llamara al método FOO2 desde la superclase, como se llama a través de Super.
¿Es posible lograr este comportamiento, es decir, tener un método de superclase Llame solo a otros métodos de superclase, incluso si están anulados en una subclase?
Solución
No estoy 100% seguro de esto, pero estoy bastante seguro de que no puedes. En Ocaml Heritance and Subtyping hay dos conceptos diferentes. Una clase puede ser un subtipo de otro tipo sin herencia. Todo lo que hace la herencia es extraer los métodos de la clase hereditaria.
El polimorfismo se logra a través de la tipificación estructural, por lo que su llamado a foo2
llama al método en bar
porque bar
ha implementado foo2
Y no porque bar
hereda de foo
(como se ha apacionado para decir, C ++, donde bar#foo2
se llama debido a una tabla de funciones virtuales.
Dicho esto, OCAML le proporciona una forma de distinguir entre métodos anulados y métodos heredados utilizando el inherit...as...
sintaxis. En bar
De tu ejemplo, o#foo1
y super#foo1
son el mismo método (ya que bar
no implementa foo1
) mientras o#foo2
y super#foo2
son métodos diferentes. A pesar de esto, no creo que haya de todos modos para que la clase hereditaria sepa que ha sido heredada y distinga entre sus métodos y los métodos anulados. Puede haber algo de sintaxis para eso, pero lo dudo mucho debido al hecho de que la herencia y el polimorfismo son conceptos independientes.
Otros consejos
No, no es. Esto es "vinculante tardío", una idea básica de OO, no específica de OCAML.
No sé qué estás tratando de hacer, pero la programación orientada a objetos quizás no sea las herramientas adecuadas, o al menos no se usa de esa manera. Si desea aprender OCAML, probablemente debería concentrarse en las partes no OO, lo cual es lo suficientemente interesante para comenzar.
Como señala Gasche, este es el comportamiento previsto y estándar para los idiomas OO.
los super#foo1
llamar, ya que bar
no anula foo1
, es exactamente equivalente a o#foo1
. los super
construir solo existe para poder llamar al foo2
método de foo
de métodos en bar
(De lo contrario, no hay forma de hacer referencia a ese método). En foo1
como se llama de bar#foo2
, o
es en realidad un bar
, No un foo
, así que invocando el foo2
el método invoca el bar
foo2
método.
Diría que si desea codificar ese comportamiento, será mejor que no use la programación orientada a objetos. Simplemente impleméntelo como funciones llamando a otras funciones.
("Ese comportamiento" como lo entendió: si llamas foo2
desde el interior del código que se ha llamado como super#foo1
, entonces exactamente el foo2
de la implementación de la superclase debe llamarse, no de las implementaciones más "específicas" de las subclases)
Es la forma más simple, más limpia y clara de continuar: las funciones del programa que hacen lo que desea.
O deberías explicarte a ti mismo y a nosotros: ¿por qué necesitas OOP? La razón no se da en el texto de la pregunta. Por qué hacer foo1
y foo2
¿Métodos en lugar de funciones independientes? (Aparte de foo1
y foo2
, puede tener algunos objetos, clases y métodos en su programa, cuando sea apropiado).
Se pregunta si esta pregunta viene de comparación con otros lnaguages
Si conoce otro lenguaje OO, es extraño que desee "ese comportamiento" de OOP: no es el comportamiento esperado en, por ejemplo, Java o C ++, porque emplean el concepto de una tabla de métodos virtuales que está asociado con cada objeto en tiempo de ejecución, por lo que al llamar a un método en su programa, se envía en tiempo de ejecución a la implementación de ese método realmente asociado con el objeto. Entonces, en resumen: Cada vez que utiliza una expresión de llamado de método en su programa, se compromete a este principio de encontrar la implementación del método ("vinculación tardía"), como lo señaló Gasche. Aunque todavía cf. Las diferencias entre OCAML y los idiomas implementados con una tabla de métodos virtuales señalados por Niki Yoshiuchi.
Formalizar toda la discusión de los comportamientos disponibles y deseados
Aunque lo que desea podría no ser el comportamiento esperado y disponible en muchos lenguajes OO populares, es imaginable y podría ser implementable en algunos sistemas OOP específicos, si uno tiene acceso a los internos de implementación de OOP.
Digamos, si en alguna implementación, super
es una estructura que contiene la tabla de métodos de la superclase (al retraso a la resolución de una llamada de método para el objeto actual), y los métodos son funciones que deben recibir el objeto (la tabla de métodos) como el primer arg, luego para realizar lo que usted quiero, uno escribiría super.foo1(super, y)
.
(De hecho, me pregunto si hay implementaciones de OOP cuyas internas están expuestas al programador y que permiten hacer tal llamada).
Mientras que el comportamiento de OOP habitual se expresaría en este sistema por this.foo1(this, y)
(dónde this
es la tabla de métodos para el objeto actual).
Tu llamada Ocaml super#foo1 y
o un Java super.foo1(y);
se traduce en este sistema "mi" como super.foo1(this, y)
. (Aunque todavía cf. las diferencias señaladas por Niki Yoshiuchi entre OCAML y idiomas como Java implementado con una tabla de métodos virtuales).
Ves las diferencias entre los 3 casos.
Apéndice. Buscando idiomas que funcionen de esa manera
Hmm, PHP podría ser un lenguaje donde ese comportamiento sería posible, pero:
- Solo en programación "a nivel de clase" (métodos estáticos), no a nivel de objeto;
- Solo cuando te das un poco de extraño "Vinculación estática tardía" en la función llamas:
#!/usr/bin/php
<?php
class Foo {
public static function foo1($y) {
echo "foo1\n";
static::foo2($y-1);
}
public static function foo2($y) {
echo "foo2\n";
if ($y > 0)
static::foo2($y);
else
echo "end\n";
}
}
class Bar extends Foo {
public static function foo2($y) {
echo "bar2\n";
if ($y > 0)
Foo::foo1($y);
else
echo "endbar\n";
}
}
class Model extends Foo {
public static function foo2($y) {
echo "model2\n";
if ($y > 0)
static::foo1($y);
else
echo "endmodel\n";
}
}
Model::foo2(3);
Bar::foo2(3);
?>
El modelo se comporta en cierto sentido como los objetos OO estándar con métodos virtuales, y barra como tú querías:
$ ./test-force-super-binding.php | head -20 model2 foo1 model2 foo1 model2 foo1 model2 endmodel bar2 foo1 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 $
(Por cierto, usando parent::
en vez de Foo::
no nos llevaría a su comportamiento deseado).
No entiendo la prupose de las especificaciones vinculantes de PHP locos como static::
, que tienen algún efecto solo para métodos estáticos (es decir, programación a nivel de clase).
Un ejemplo de C ++ analógico no da un comportamiento de nivel de objeto OO de forma predeterminada:
#include<iostream>
class Foo {
protected:
static void foo1(int y) {
std::cout << "foo1" << std::endl;
foo2(y-1);
}
public:
static void foo2(int y) {
std::cout << "foo2" << std::endl;
if (y > 0)
foo2(y);
else
std::cout << "end" << std::endl;
}
};
class Bar: public Foo {
public:
static void foo2(int y) {
std::cout << "bar2" << std::endl;
if (y > 0)
foo1(y);
else
std::cout << "endbar" << std::endl;
}
};
int main() {
Bar::foo2(3);
return 0;
}
- Le da a tu comportamiento buscado:
$ ./a.out | head -10 bar2 foo1 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 $
Incluso sin ningún calificador especial en la llamada de función en el código para Bar::foo2()
, así que no es interesante para nosotros.
¿Qué pasa con los métodos estáticos de Java?