SinOsc.class returns Meta_SinOsc?

Hi list,

I am trying to get my head around the following:

Querying SinOsc.class will return
Meta_SinOsc

Meta_SinOsc.class returns
Class

I am wondering about the meaning of Meta_* and why SinOsc.class doesn’t
return something like
UGen
?

Thanks for all hints, much appreciated!
Peter

Try SinOsc.superclass or SinOsc.allSuperclasses.

Every class lives in pairs: TheClass for instance methods, and Meta_TheClass for *classMethods. It’s a bit odd though:

  • 1.0.class should give you class Float because 1.0 is an instance of Float.
  • But Float isn’t really an instance of Meta_Float…

I’m not sure of the reason why .class navigates from an instance of a class to its class, but from the class to its meta-class… but that’s what it does.

hjh

1 Like

Interesting, I didn’t know this!

It seems like .class in only intended for instances and .superclass is only meant for classes; in fact, calling the latter on a class instance throws an error. I’m wondering if calling .class on a Class instance shouldn’t throw an error as well…

I’m not sure of the reason why .class navigates from an instance of a class to its class, but from the class to its meta-class… but that’s what it does.

I think it kind of makes sense. SinOsc is not an instance of PureUGen, it only inherits from it. It is, however, an instance of Meta_SinOsc which in turn is an instance of Class. These are two different relationships, that’s why we have two different methods (class and superclass).

Now, here’s comes the baffling thing: Class is a subclass of Object. This means objects are instances of classes, but classes themselves are also objects. It’s a classic condundrum in many object oriented languages.

For Python, see for example metaclass - what is the difference between type class and object class in python - Stack Overflow.

Similarly, in C# objects are instance of classes – with Object Object Class (System) | Microsoft Learn) being the root class – but classes themselves are also objects and inherit from Object, see Type Class (System) | Microsoft Learn.

In fact, these two relationships form two hierarchies:

  1. class inheritance (with .superclass):
    [instance] -> SinOsc -> PureUGen -> UGen -> Object
  2. classes as objects (with .class):
    SinOsc -> Meta_SinOsc -> Class -> Meta_Class -> Class -> Meta-Class -> ad inf.

We can also see the same two relationships with Class itself:

  1. Class -> Object (with .superclass)
  2. Class -> Class -> Meta_Class -> Class -> Meta-Class -> ad inf.

I’m not even trying to resolve this conundrum, I’m already feeling dizzy. :slight_smile:


In a prototypical language like Lua or JS there is indeed a symmetric relationship between objects and classes and superclasses.

For example, that’s how you get the “superclass” of an instance in JS:

Object.getPrototypeOf(Object.getPrototypeOf(foo));

You call getPrototypeOf to get the “class” (= the prototype), then a second time to get the “superclass” (= the prototype of the prototype).

Similarily in Lua:

getmetatable(getmetatable(obj));

BTW, that’s what I love so much about Lua: it is very easy to reason about and there is very little magic going on :slight_smile:

Addendum:

In dynamic OOP or prototypical languages the class hierarchy manifests itself directly as a runtime dependency chain. Here’s an example in pseudo-code:

Class Foo {
    function foo() {}
}

Class Bar extends Foo {
    function bar() {}
}

x = new Bar()
x.foo()

Here’s what happens in the last line in a dynamic OOP or prototypical language:

  1. foo is not found in instance → look in class/prototype Bar
  2. foo is not found in Bar → look in parent class/prototype Foo
  3. foo is found → call it with x

The instance x and the classes Bar and Foo actually reference each other. Some languages, like Lua or JS, even allow to change the inheritance chain at runtime!

Now, in a compiled OOP language the class hierarchy is fixed at compile time, so there is no need for runtime lookup across classes. Instead, classes typically copy all methods of their superclasses. The example above would generate the following lookup sequence (depending on the language, either at compile time or runtime):

1. `foo` is found in `Bar` -> call it with `x

While Foo and Bar belong to the same class hierarchy, they are not instances of each other! In some languages, the classes themselves might still be objects (e.g. to enable runtime reflection), but they are instances of some metatype (e.g. Class or Type), not instances of each other!

This explains why such languages, including sclang, need two different methods for querying the class hierarchy:

  1. a method to get the class of an instance (in sclang: Object.class)
  2. a method to get the superclass(es) of a class (in sclang: Class.superclass)

Thanks James and Christof for your detailed response. I feel like I need
to re-read instance methodes versus class methods. Seems like I have
touched something which is not entirely structured either. Again thanks
for the explanations!

best, P

I’ve always thought about this from the interpreter’s perspective. If you have something, any object, and want to call a method on it, you need to do some operation to look up the method table. We call this operation ‘getting the class’ - from the interpreter’s point of view, a class is synonymous with the method table. Whether or not the object is an instance of the result of ‘class’ isn’t relevant to the interpreter.

Therefore, to have class methods requires that X().class.class return something specialised on the class ‘X’. You could use another operation (not class), but the interpreter would need to know which one to use, in other words, you’d need a new ‘kind’ of thing that isn’t an object.

As I tried to point out in my posts above, a class like SinOsc is indeed an object and as such is an instance of a class, but that class is Meta_SinOsc and not PureUGen. To get the super class of a class, you have to call .superclass.

Yes I agree! Merely saying that I think this behaviour is more obvious when considering the interpreter and how it looks up methods.

I see, that’s a good point!

Great and inspiring discussion!

What would be a good way to sum all this up in one or two sentences, as
for example to be included in the help system?

best, P

Here’s a diagram I drew in the back of my SuperCollider book a long time ago… Sorry it’s gotten a bit hard to read

2 Likes

Hey, that’s a great graphic!

In particular, I love the following explanation:

  1. .class → what is the receiver an instance of?
  2. .superclass → what does the receiver inherit from?

This makes it very clear that .class is meant for objects and .superclass is meant for classes. But then again, classes are themselves objects and as such they are also instances of a class (= the metaclass).

I think the help could have a dedicated entry for Class.class that explains the difference between .class and .superclass.

One nitpick: there is no inheritance relationship between Object and nil! Object.superclass only returns nil to indicate that it has no superclass. After all, nil is not a class and nil.subclasses throws an error. Therefore I would remove the blue arrow between Object and nil.

Apart from that, the chart is really helpful! The circular relationship between Class and Meta_Classes is still mend bending, though. :face_with_spiral_eyes: :exploding_head: