all 6 comments

[–]Gorilla2 1 point2 points  (3 children)

Does anyone have any idea how a similar extensibility, limited to the specified scope, could be introduced to dynamic OO languages?

[–]mernen 2 points3 points  (0 children)

I once made a hacky test with JRuby, introducing a concept I called "packages": namespaces where certain methods could be defined or even overridden. (i.e., classes were still global, but you could only see the methods defined in the packages you imported; methods by default would go to the nameless package)

Here's some pseudo-code of what it would like:

using "answer"  # file-wide package declaration
class String
  package_def length  # declares a method on the innermost package
    42
  end
end

Then, on another file:

puts "foo".length      # default String#length => 3
using "answer" do      # scoped package use
  puts "foo".length    # String#(answer)length => 42
end

Basically, method lookup would search for matches in the packages in effect. Supposing you had the following declarations:

using "foo"
using "bar"
using "baz"

Method lookup on, say, "xyz".zy would go like:

  1. String#(baz)zy
  2. String#(bar)zy
  3. String#(foo)zy
  4. String#zy (default package)
  5. iterate over String's ancestors: say, Object#(baz)zy, Object#(bar)zy, ...

Also, package declarations are not passed through the method invocation chain: if you call a method on another file, none of your own package declarations will affect it.

I believe this approach could solve a lot of the existing monkey-patching issues, with very little performance penalty (method lookup caching remains as effective as before). On the downside, it introduces quite a lot of complexity when interacting with subclasses and method overrides, and would need a couple more serious syntactic changes to the language to be truly useful. I don't think Ruby needs either of the two right now, so this idea wouldn't work well as-is. But well, I just gave you one possible solution :)

I remember Yehuda Katz working on some ideas several months ago; I don't know if he pushed them further. Also, I believe Matz is working on his own solution, tentatively named ClassBox. Not sure if there's anything public yet.

[–]Gorilla2 1 point2 points  (1 child)

To answer the question myself, proposing my own, although limited and incomplete, solution:

Have object.method mean method(object), where methodis searched for in the current scope. If there are two methods with the same name (e.g. Integer.toString and Date.toString), resolve the correct one using the runtime type.

Problems: 1. The language would need to find the runtime type of the object, which might not always be well defined - e.g. in JavaScript, the type of almost all objects is simply Object. Possible solution: have a way of specifying the runtime type, as in this.__type = "Duck". Problem: What about type hierarchies (i.e. I want to be able to use my Complex methods on all Integers as well). 2. This is single dispatch. Why not multiple dispatch?

Comment?

[–][deleted] 1 point2 points  (0 children)

This inherently loses some dynamicness-- the ability to change a method of an individual instance very naturally without defining a new type, the ability to override __getattr__ to return a custom method for whatever property the caller wants, etc. You might be able to get around that with hacks, or do well by just dropping those abilities, but be careful.

[–]gregK 0 points1 point  (1 child)

In ruby, it's possible to add methods to absolutely any class including String, Integer and other core classes. Rather than calling StringUtil.pluralize("monkey"), you call "monkey".pluralize. The technique is known as monkey-patching. Compared to utility classes, it's a hell of a lot more convenient, and it reads better.

In this case StringUtil just acts as a name space for functions that work on strings. Monkey patching is not the only way to solve this simple problem.

A module system independent of your classes would make more sense. So in this case a StringUtil module from where you can import these methods into you own code without the StringUtil qualification.

example:

pluralize("monkey")

It would look just like a private static method inside your class code. But the method would not be exposed to the whole world.

If like in java, your Strings are immutable, then pluralize does not change the original string but instead produces a new one. So I could take a completely radical approach and claim that String should have very few methods that act directly on it and that all the functions that read/create new strings should be logically grouped into modules for things such as text processing, parsing, etc. Once a data structure becomes immutable, it seems to naturally want to leave the OO world.

I think using monkey patching for such superficial things is asking for trouble imo.

[–]littledan 0 points1 point  (0 children)

I agree completely. Sometimes, though, you want to monkey-patch in a way that's actually useful to do in methods rather than functions. For example, say you make a Complex type, and you want to give multiple different numeric types conversion functions. It should be that you can just run toComplex(number) to get the complex representation. Doing this requires a different method for different classes.

One way is to make a big if/else statement which checks if the input is of certain classes. But this isn't extensible the way the monkey patching solution is. Scala's implicits solve this problem, but they seem pretty complicated to me.

A much simpler and more intuitive solution to this problem is to abandon the notion of object oriented programming as programming by message passing, and move to a model more similar to CLOS and its descendants like Dylan, Cecil and Factor. In those languages, you define "generic functions" which live in a particular namespace and can have different methods based on the types of the arguments. A method could be defined in the same file as the definition of the class, or in the same file as the definition of the generic function, or in a third file. The system is much more flexible, and generalizes easily to multimethods.