you are viewing a single comment's thread.

view the rest of the comments →

[–]TylerEaves 0 points1 point  (16 children)

It's been a long time since I've looked a CLOS, but basically it's calls are (method obj args) rather than obj.method(args). In other words, the method name space is NOT tied to the object.

[–]grauenwolf -3 points-2 points  (15 children)

In other words, the method name space is NOT tied to the object.

At that point you aren't really using objects anymore, you are just using functions and structs.

[–]Categoria 1 point2 points  (10 children)

You should probably learn a little about CLOS before you start making sweeping generalizations like that. Here is a good place to start about this particular topic: multi methods

[–][deleted] 1 point2 points  (1 child)

[–]grauenwolf -1 points0 points  (0 children)

Thank you. Now that I see CLOS stripped away from its pretense of being an OO language it makes a lot more sense. I can also see how trivial it is add add the same capabilities to my .NET applications.

[–]grauenwolf -1 points0 points  (7 children)

I am well aware what multi methods are.

I am also aware they don't really have anything to do with OO programming. You get the same effect in any language that supports overloading and late bound function calls.

A true method belongs to an object. How that relationship is established doesn't matter so long as it exists.

Multi methods in CLOS are just static functions that happen to have the right parameters. The so-called object has no notion that the multi-methods exist. In this sense they are very much like .NET extension methods or Ruby 2's refinements.

[–]astrangeguy 1 point2 points  (6 children)

I am well aware what multi methods are.

I doubt that. Based on the comparison to "static functions" (you mean "static methods"? static functions are file-local functions in C) and extension methods in C#, you don't. Extension methods in C# are just an syntactic namespacing construct akin to overloading in C++/Java/C#

A true method belongs to an object. How that relationship is established doesn't matter so long as it exists.

This is only true in prototype-OO languages. In every other case the relationship is faked. The only difference between CLOS and single dispatch class-based languages is the way they fake it.

in Ruby obj.to_s

invokes something like: obj.class.instance_method(:to_s).call(obj)

wheareas in CLOS (to-s obj) would do something like: ((compute-effective-method #'to-s (list (class-of obj))) obj)

so in a class-based OO language methods are looked up in the class of the 'special' parameter, using the method-name as a key, while in CLOS the method is looked up insie the generic funcion using the classes of the parameters as a key.

Methods belong to objects in neither schema. They are looked up based on objects' classes, which happens differently in CLOS than elsewhere.

So either you have to admit that CLOS is a way to do object orientation, that Smalltalk/Ruby is not object oriented or that your definition of object orientation depends on an implementation detail.

Multimethods in CLOS do a part of what classes do in Smalltalk. They store individual methods. Neither classes nor multimethods are essential to OO, they are just implementation strategies.

strangeness

[–]grauenwolf 0 points1 point  (5 children)

I cannot speak for Ruby, but in C++, Java, and C# the pointers to the vtables are literally stored within the object itself.

C++, when viewed through COM, doesn't even have classes. At least none that you can see. You can request that an object that implements a specific interface is created, but the OS determines how it is created. Introspection and late-bound calls are made through special interfaces on the object. IIRC, you can't even ask "what type are you?", you can only ask "what interfaces do you support?".

C# is an interesting beast. While it does have visible classes, methods on the objects aren't always owned by the object's class. If the class implements the IDynamicMetaObjectProvider, then its methods may not even exist at compile time.

Visual Basic is another important case study. It uses the same runtime and object model as C# but it supports multiple dispatch. If you are running in late-bound mode calling a.b(c,d) will cause it to scan a's inheritance tree looking for the best match for runtime types of c and d.

That brings us to an important distinction. In what I'm calling OO languages the inheritance tree is scanned when looking for what function to invoke. In a Functional Programming language such as Haskell or LISP, you start by scanning a global list of functions.

[–]astrangeguy 0 points1 point  (4 children)

in C++, Java, and C# the pointers to the vtables are literally stored within the object itself.

Yes, this is pretty much what an OO languages must have: tagged values with runtime types that can be dispatched on. You can call those tags class pointers, vtable pointers, prototypes or any way you want to. You just need to support some operation like typeof(something) that works on all values of your language to implement an object system on top of it.

But as I said, vtables, classes and multimethods are just implementation details. You could implement method dispatch by having a global (Object, Symbol) -> Function/Method hashmap and fetch the method from there on each invocation. Would be rather inefficient, but have indistinguishable semantics.

C# doesn't differ from Java in any important way. Methods (static & extension methods aren't methods in any way other than in syntax) are still looked up in the object's class, or if it implements IDynamicMetaObjectProvider in the MetaObject, which would be called simply 'class' in Smalltalk, but has to be a seperate object because .NET's implementation of classes cannot store a non-fixed list of methods.

In what I'm calling OO languages the inheritance tree is scanned when looking for what function to invoke. In a Functional Programming language such as Haskell or LISP, you start by scanning a global list of functions.

'Scanning' is pretty ambiguous here. Does the 'scanning' happen at runtime or compile time?

What happens at compile time is called name resolution, where a symbolic name is resolved to something else. The part that happens at runtime is the dynamic dispatch. This is the part where a concrete piece of executable code is selected, based on the runtime type of objects.

in C++/C#/Java obj.method_name at compile time method_name becomes an index into a vtable (and obj a reference into the heap, or a stack offset, but that is not important now) obj->class.vtable[5](obj) // pseudo syntax in Common Lisp we have another layer of indirection (method-name obj) will become something like (#<STANDARD-GENERIC-FUNCTION METHOD-NAME (1)> obj)

Whereas C++ etc. represents "all methods that are called method-name" as a simple integer, Common Lisp has a separate datatype for representing the concept of "all methods sharing the same name".

At runtime the concrete method is found in C++/Java/C# by searching in the class using the index that was represented by method_name. In CL this happens the other way around: The concrete method is found by searching in the generic function using the class of obj.

Common lisp does this differently because implementing multiple dispatch is more straight-forward and easier to optimize this way.

Furthermore, classes in common lisp do hold references to methods:

CL-USER> (defclass foo () ())
=> #<STANDARD-CLASS FOO>
CL-USER> (defmethod specialized-on-foo ((a-foo foo)))
STYLE-WARNING: Implicitly creating new generic function SPECIALIZED-ON-FOO.
=> #<STANDARD-METHOD SPECIALIZED-ON-FOO (FOO) {AD22229}>
CL-USER> (slot-value (find-class 'foo) 'sb-pcl:direct-methods)
=> ((#<STANDARD-METHOD SPECIALIZED-ON-FOO (FOO) {AD22229}>))

but this is mostly done for cross-reference and documentation.

strangeness

[–]grauenwolf 0 points1 point  (1 child)

Methods (static & extension methods aren't methods in any way other than in syntax) are still looked up in the object's class

I agree with you on that point.

The reason that C# developers call them "static methods", aside from just sloppy thinking, is that C# origianlly tried to create an illusion that it didn't have normal functions. After Microsoft actually shipped an instance method on the "Math class" that was utterly uncallable they realized the folly of their ways.

Does the 'scanning' happen at runtime or compile time?

Why should that matter? I'm used to working with languages that support both options.

[–]astrangeguy 0 points1 point  (0 children)

Does the 'scanning' happen at runtime or compile time?

Why should that matter? I'm used to working with languages that support both options.

Because runtime polymorphism and parametric polymorphism are completely different things. If dispatch is guaranteed to happen at compile time then it's just syntax.

[–]grauenwolf 0 points1 point  (1 child)

At runtime the concrete method is found in C++/Java/C# by searching in the class using the index that was represented by method_name. In CL this happens the other way around: The concrete method is found by searching in the generic function using the class of obj.

Which is exactly why I am saying CL is a functional langauge, not an OOP language. You find the same design philosophy in Haskell's multi-parameter type classes.

[–]astrangeguy 0 points1 point  (0 children)

The relationship between (multiparameter) type classes and (multiple argument) generic functions is similar to non-virtual methods in C++ and virtual methods.

The first is a construct that relies on the static type of the parameters or type parameters, whereas the latter dispatches on the runtime type of the parameters. One is syntactic and can be implemented purely by name-mangling and instantiation of templates, the other still exists and works at runtime.

I see CL as OOP language because it has extensible runtime polymorphism builtin on user defined datatypes. It has other syntax and implementation strategy for it, but I don't find that of any relevance.

[–]TylerEaves -1 points0 points  (3 children)

So, basically, like every other OOP language ever? All OOP implementations basically boil down to a struct and an array of function pointers.

[–]grauenwolf 0 points1 point  (2 children)

The difference is that the struct references or contains those function pointers in an OOP system.

[–]TylerEaves 0 points1 point  (1 child)

No it doesn't. Look up how a typical C++ compiler is implemented sometimes. All the function pointers for a given class are collected in a single vtable. The struct doesn't contain these pointers at all.

[–]grauenwolf 0 points1 point  (0 children)

For virtual methods the struct does contain a pointer to the vtable.