Writing Classes: init & new clarification needed

Hello there!

I’ve been following the Writing Classes help file but I got it all wrong. My classes init is called several times (once for every custom parent class) ! This works but, hum…

This is because I’ve been stupidly copy-pasting this everywhere:

*new { |parent, bounds|
	^super.new.init;
}

There’s this warning on the doc:

If the superclass also happened to call super.new.init it will have expected to call the .init method defined in that class (the superclass), but instead the message .init will find the implementation of the class that the object actually is, which is our new subclass. In such cases, a unique method name like myclassInit should be used.

But I hope there’s more than that?


Now let’s say I have 1 000 classes, with class 1 being parent of class 2, class 2 parent of class 3, etc…

I think they all implement:

*new { |parent, bounds|
	^super.new;
}

Called from class 1000, this goes bottom-up, up until Object.new is called. Now, I’d like to init every class top-down. Is this possible:

*new { |parent, bounds|
	var me = super.new;
	me.init;
	^me
}

init {
	do something specific to this class
}

If me.init; always look at class 1000’s initmethod, then we’re screwed right ? It means every class must have a unique init method name ? Can ^super.init be helpful here ?


As a side question, in terms of syntax, are we removing the ; character from return statements within .sc files:

*new { |parent, bounds|
	^super.new
}

Thanks for your help!

Here’s my approach to this…

Don’t use init methods if possible. Put all the initialisation logic in the new method, if there are parts of the object that are interconnected, make them before you call new and then assign them. This is how languages like rust do it, meaning you never have an object in an uninitialised state.

Also, it is best to avoid large class hierarchies, and I’d say a good idea to have simple/trivial constructors in parent classes (if they are needed at all).

Then you can use newCopy instead.

1 Like

Thanks :slight_smile: .

This makes sense. It might be worth writing this within the documentation. I used new + init because that’s how it’s done in the base code, so I was thinking there might be some hidden reasons classes should be designed this way. I think there might be cases where deferring init would make sense, but not in the SuperCollider context, especially if the docs points towards super.new.init.

Some server resources have this uninitialised state where you make them language side, but don’t ask the server to create the resource until later. I think this is where the init method gets used. It might have been preferable to have another class represent the uninitialised resource.

The problem is that *new is a class method and therefore it can’t do anything with instance variables except through getter/setter methods, or calling super.newCopyArgs. So if you are e.g. subclassing a class that already has some instance variables populated via newCopyArgs, I don’t know of any way to populate any extra instance variables except to use an init method.

Here’s an example from one of my classes whose superclass populates a common set of instance variables with newCopyArgs

  *new { |startTime, duration, offset = 0, color, name, defName = 'default', args, target, addAction = 'addToHead', mute = false, func, doPlayFunc = false|
    args = args ?? [];
    func = func ? defaultFunc;
    ^super.new(startTime, duration, offset, color, name, mute: mute).init(defName, args, target, addAction, func, doPlayFunc);
  }

  init { |argDefName, argArgs, argTarget, argAddAction, argFunc, argDoPlayFunc, argBus|
    defName = argDefName;
    args = argArgs;
    target = argTarget;
    addAction = argAddAction;
    func = argFunc;
    doPlayFunc = argDoPlayFunc;
  }

I still think Jordan’s advice is still sound – this should be avoided if possible, and is a good reason to avoid complicated class hierarchy (the canonical way seems to be to then add differently named init methods in further subclasses)

… whereby all of those member variables have to be public. To initialize private variables (those not exposed by getter/setter methods), an instance init method is the only way.

Many pattern classes expose all of their member variables, and do follow the no-init model.

var <>param;
*new { |param|
    ^super.new.param_(param)
}

But this wouldn’t work in, for example, my MixerChannel class because 1/ I don’t want everything to be exposed and 2/ as Jordan pointed out, some resources have to be created and state maintained, and it would be exceedingly difficult to manage that all in a class method.

But I wouldn’t create 10 levels deep of subclasses below MixerChannel either.

The SC class library has a lot of examples of subclassing, so we tend to reach for that first. Sometimes an Adapter or Decorator is better, though. For example, the ddwProto quark handles some of the problems of object prototyping by defining a class that holds an Environment without actually being an Environment: “has-a” rather than “is-a.” Because it doesn’t inherit from Environment, there is less surface area of unwanted behavior from Environment to manage. (Also, this approach was necessary when creating “subclasses” of GUI objects back in the days when we had CocoaGUI on Mac and SwingGUI on Linux and Windows. Subclassing would require code duplication. SCViewHolder made it possible to build new behaviors once based on whichever widgets were available on the system where it was running.)

So I wonder if this case of deeply nested subclasses might be better modeled as a flat (or flatter) set of different Decorators that manipulate an object held inside it. Some cases, maybe not, but some cases I’m pretty sure would work better that way.

hjh

I’ve always found this to be a painful area of SC, I suppose you could pass extra arguments through to the the underlying newCopyArgs. One problem arises with multiple levels of inheritance and ensuring the instance variable goes in the correct place.

A little while ago a submitted two prs relevant to this, one was redoing the implementation of keywords args, and the other was making newCopyArgs work with keywords arguments. Together you could do something like the following… (Away from pc so can’t test but this should work)…

Base {
   var a, b;
   *new { |a, b ...args, kwargs| 
      ... some initialisation logic with a and b
      ... Check args is empty or throw 
      ^this.performArgsSuper(\newCopyArgs, [a, b], kwargs)
   }
}

Derived : Base {
   var c;
   *new { |a, b, c... args, kwargs| 
      ^this.performArgsSuper(\new, [a, b] ++ args, [\c, c] ++ kwargs)
   }  
}

This way, you can assign to c, without requiring it to be public, and you don’t need to worry about the instance variable ordering because they are set by name. This approach doesn’t solve issues of running extra initialisation logic though.

1 Like