4

To be honest, I don't know much about OCaml's object system. The following snippet illustrates my problem:

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

When executed, the snippet produces the following output:

bar2
foo1
bar2
foo1
bar2
endbar

, showing that the superclass method foo1 (called via super#foo1) executes the overriden method foo2 from the subclass. I would instead have expected it to call the method foo2 from the superclass, as it is called via super.

Is it possible to achieve this behaviour, i.e. have a superclass method call only other superclass methods even if they are overriden in a subclass?

imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
ulricha
  • 123
  • 4
  • 1
    I think that the current title "Overriden methods in OCaml" is too general and not informative. 1) The peculiarity you are asking about is not really a peculiarity of Ocaml, so nobody will guess what you are wondering about from this title; "in Ocaml" in the title is not enough for being informative. 2) "overridden methods" is too broad. I'd change the topic to smth like **"How to force calling only superclass's methods despite that they have been overridden (in Ocaml)"**. – imz -- Ivan Zakharyaschev Jan 28 '11 at 21:27

4 Answers4

4

No it's not. This is "late binding", a basic idea of OO, not specific to OCaml.

I don't know what you're trying to do, but object-oriented programming is perhaps not the right tools, or at least not used that way. If you want to learn OCaml, you should probably concentrate on the non-OO parts, which is interesting enough for a start.

gasche
  • 31,259
  • 3
  • 78
  • 100
1

I'm not 100% sure on this, but I'm pretty sure you can't. In OCaml inheritance and subtyping are two different concepts. A class can be a subtype of another type without inheritance. All inheritance does is pull in the methods from the inherited class.

Polymorphism is achieved via structural typing, so your call to foo2 calls the method in bar because bar has implemented foo2 and NOT because bar inherits from foo (as apposed to say, C++, where bar#foo2 is called due to a virtual function table look up).

That said, OCaml does provide you with a way to distinguish between overridden methods and inherited methods using the inherit...as... syntax. In bar from your example, o#foo1 and super#foo1 are the same method (since bar doesn't implement foo1) whereas o#foo2 and super#foo2 are different methods. Despite this, I don't think there is anyway for the inherited class to know that it has been inherited and distinguish between it's methods and the overridden methods. There might be some syntax for that but I highly doubt it due to the fact that inheritance and polymorphism are independent concepts.

Niki Yoshiuchi
  • 16,883
  • 1
  • 35
  • 44
  • I don't see the idea of your answer. 1) You write: " In OCaml inheritance and subtyping are two different concepts." If inheritance and subtyping were coupled together, would this help forcing the calling of only superclass's methods after some point, as the asker wants? – imz -- Ivan Zakharyaschev Jan 28 '11 at 21:05
  • 2) You write "... due to the fact that inheritance and polymorphism are mutually exclusive concepts." What do you mean by "inheritance and polymorphism are mutually exclusive concepts"? – imz -- Ivan Zakharyaschev Jan 28 '11 at 21:17
  • Still, I have ultimately understood that the "pulling in" concept you refer to has something to do with the question. I now see the picture: an inheriting class pulls in the implementations of the methods it doesn't implement itself, hence when such a pulled-in method is executed, it's like an **independent copy** of the original implementation, it can **know nothing about the "context" of the original base class**, all calls are simply newly resolved in the new "context" of the inheriting class... – imz -- Ivan Zakharyaschev Jan 28 '11 at 22:04
  • @imz - When I said that inheritance and polymorphism are mutually exclusive what I mean is that in C++ inheritance is necessary for polymorphism - methods are called using a virtual table look up (aka, late binding as gasche pointed out). This allows you to force the base class to use the base classes implementation using `base::method`. In OCaml this doesn't work because `bar` isn't of type `foo`, rather they both are of type `object: method foo1 int -> unit; etc...`. – Niki Yoshiuchi Jan 28 '11 at 22:09
  • 1
    As you pointed out in another comment `base::method` is unnecessary, but this seems to be what alex intended. You're right about the class not know about the "context" of the original base. – Niki Yoshiuchi Jan 28 '11 at 22:13
  • I think "inheritance and polymorphism are independent" would be more clear; I understand "exclusive" as "being impossible together", "incompatible". – imz -- Ivan Zakharyaschev Jan 28 '11 at 22:34
1

I'd say that if you want to hardcode that behavior, you'd better not use object-oriented programming. Simply implement it as functions calling other functions.

("that behavior" as understood by me: if calling foo2 from inside the code that has been called as super#foo1, then exactly the foo2 from the implementation of the superclass should be called, not from the more "specific" implementations from subclasses)

It's the simplest and cleanest and clearest way to proceed: program functions that do what you want.

Or you should explain to yourself and to us: Why do you need OOP? The reason for it is not given in the question text. Why make foo1 and foo2 methods rather than independent functions? (Aside from foo1 and foo2, you may have some objects and classes and methods in your program, where appropriate).

Wondering whether this question comes from comparison to other lnaguages

If you know another OO language, that's strange that you want "that behavior" from OOP: it's not the behavior expected in, say, Java or C++, because they employ the concept of a table of virtual methods that is associated with each object at run-time, so when calling a method in your program, it gets dispatched at run-time to the implementation of that method actually associated with the object. So, in short: whenever you use a method-calling expression in your program, you commit yourself to this principle of finding the implementation of the method ("late binding"), as pointed out by gasche. Although still cf. the differences between OCaml and languages implemented with a virtual methods table pointed out by Niki Yoshiuchi.

Formalizing all the discussion of the available and wanted behaviors

Although what you want might not be the expected and available behavior in many popular OO languages, it is imaginable and could be implementable in some specific OOP systems, if one has access to the OOP implementation internals.

Say, if in some implementation, super is a structure holding the methods table of the superclass (to fallback to when resolving a method call for the current object), and the methods are functions which must receive the object (the methods table) as the 1st arg, then to perform what you want, one would write super.foo1(super, y).

(I actually wonder whether there are OOP implementations whose internals are exposed to the programmer and that allow doing such a call.)

Whereas the usual OOP behavior would be expressed in this system by this.foo1(this, y) (where this is the methods table for the current object.)

Your OCaml call super#foo1 y or a Java super.foo1(y); translates into this "my" system as super.foo1(this, y). (Although still cf. the differences pointed out by Niki Yoshiuchi between OCaml and languages like Java implemented with a virtual methods table.)

You see the differences between the 3 cases.

Appendix. Looking for languages that would work that way

Hmm, PHP could be a language where that behavior would be possible, but:

  • only on "class-level" programming (static methods), not object-level;
  • only when you hardcode some strange "late static binding" at the function calls:

#!/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);
?>

The Model behaves in a sense like standard OO objects with virtual methods, and Bar as you wanted:

$ ./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
$ 

(BTW, using parent:: instead of Foo:: wouldn't get us at your wanted behavior.)

I don't understand the prupose of the insane PHP's binding specifications like static::, which are of some effect only for static methods (i.e., class-level programming).

An analoguous C++ example doesn't give an OO object-level behavior by default:


#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;
}

-- it gives your wanted behavior:

$ ./a.out | head -10
bar2
foo1
foo2
foo2
foo2
foo2
foo2
foo2
foo2
foo2
$ 

even without any special qualifier at the function call in the code for Bar::foo2(), so not interesting for us.

What about Java's static methods?.. (Do they differ from C++ and give us a virtual-method-like resolution of the function-calls by default?)

Community
  • 1
  • 1
imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
1

As gasche points out, this is the intended and standard behavior for OO languages.

The super#foo1 call, since bar does not override foo1, is exactly equivalent to o#foo1. The super construct only exists to be able to call the foo2 method of foo from methods in bar (otherwise there is no way to reference that method). In foo1 as called from bar#foo2, o is actually a bar, not a foo, so invoking the foo2 method invokes the bar foo2 method.

Michael Ekstrand
  • 28,379
  • 9
  • 61
  • 93
  • 1
    In C++ you can however write `foo::foo2(y-1)` and `foo`'s method will be called. There is no equivalent in OCaml. – Niki Yoshiuchi Jan 28 '11 at 16:56
  • @Niki Yoshiuchi: if you need to write `foo::foo2(y-1)`, you could write the implementation of `foo::foo2` as a non-member function, say, `basic_foo2`, use it in `foo::foo2` and call it in all other places where there is a need. Thus, in my opinion,`foo::foo2(y-1)` is not an essential feature of C++, `basic_foo2(y-1)` would be as good and implementable in other languages as well, e.g., in Ocaml. Also, it's not the remedy for the question asked. – imz -- Ivan Zakharyaschev Jan 28 '11 at 20:45
  • @Niki Yoshiuchi: To be correct and clear, I should have written `basic_foo2(this, y-1)` to call the non-member function. Or it could be implemented in another intentionally non-overridable fashion. Using a construction expressing the intention of not being overridden is IMO cleaner. – imz -- Ivan Zakharyaschev Jan 28 '11 at 20:57
  • @Niki Yoshiuchi: Thanks for the answers to my comments (in the comments elsewhere)! Ah, now I see you thought that alex merely wanted something like `base::method` in the definition of `foo1` in `foo`; that's why you are pointing at it. I thought that the question is about smth more contrived: whenver you call a function as `base::foo1` (`super#foo1`), all calls made inside it are resolved wrt the `base` class, but if called without the qualifier (`foo1`) in an inheriting class, the calls work as usual in OOP. – imz -- Ivan Zakharyaschev Jan 28 '11 at 22:25
  • 1
    @imz - ah, I see the confusion, I read the question wrong. What alex wants is not expected behavior for any OOP language I know of. – Niki Yoshiuchi Jan 28 '11 at 22:35
  • @Niki Yoshiuchi: No, now I don't think you read the question wrong. Such reading is possible, but as for me, I probably even didn't consider it because it seemed to me not to even be a problem (cf. my comment above about `base::method` being "unnecessary"). – imz -- Ivan Zakharyaschev Jan 28 '11 at 22:41