How to set/get values when writing Classes?

I’ve read the documentation (Writing Classes, Messages, etc), but couldn’t figure out what I’m actually doing.

I’m trying to create a GUI Quark, and I want to use a Class to store a palette, i.e. storing information like the border size. Then I’d like other classes to fetch those values and use them when instancing Objects.

So here is the Class with the palette :

GM {
	classvar <>mainColor;

	*initClass {
		mainColor = Color(0.75, 0, 0.333);
	}
}

Then a custom UserView :

GMUserView : UserView {

	classvar <>mainColor;

	*initClass {
		Class.initClassTree(GM);
	}

	init {
		mainColor = GM.mainColor;
	}
}

When trying to access GMUserView().mainColor, it yields a “Message not understood” error, but the .mainColor still appears in the documentation Class Methods.

I can see that I’m not fully understanding setters/getters, because I also had a problem with <>variable which was solved by declaring the message (string { ^myString }).

If anyone knows what documentation I lack, it would be helpful to me to read it,

Many thanks,
Simon

Hum, this is better :

GMUserView : UserView {
var <>mainColor;

var instead of classvar, obviously.

But it now returns nil

Try this:

GM {
// instance var var <>mainColor;

// create new instance of this class *new {
// call the init instance method super.new.init()
}
// instance method sets the instance variable
init {
mainColor = Color(0.75, 0, 0.333);
} }

I don’t fully understand how this works.

I’m not trying to create any instance of GM, in itself GM.mainColor works and returns the correct data, GM().mainColor doesn’t (and shouldn’t).

Maybe this is what I shouldn’t do : I’m trying to take advantage of the Class being a single Object that can hold datas.

What I don’t understand is why I can’t get the data stored into GM.mainColor when instancing an Object, and assign it to a newly instanced Object variable.

init is an instance method, you haven’t made an instance yet.

GM {
	classvar <>mainColor;

	*initClass {
		mainColor = Color(0.75, 0, 0.333);
	}
}

GMUserView : UserView {

	classvar <>mainColor;

	*initClass {
		Class.initClassTree(GM);
		mainColor = GM.mainColor;
	}

}

Now GM.mainColor == GMUserView.mainColor

Whereas, to get an instance…

GM {
	classvar <>mainColor;

	*initClass {
		mainColor = Color(0.75, 0, 0.333);
	}
}

GMUserView : UserView {
	var <>mainColor;

	*initClass {
		Class.initClassTree(GM);
	}
	*new{
		^super.new.init
	}
	init{
		mainColor = GM.mainColor;
	}
}

Now g = GMUserView(); GM.mainColor == g.mainColor

The call to super.new internally calls malloc and asks the operating system for a piece of memory for the object to live, until then, no instance methods, only class methods. The difference is that little asterisk. Also, A() calls new by default

I’m trying to take advantage of the Class being a single Object that can hold datas.

Also, if the values are constant, you could write:

K { *k { ^42 } }

Also, if you want variants (different configurations), you could write:

K { *x { ^(k: 42, c: 3.141) } *y { ^(k: 23, c: 2.718) } }

(So that K.x.k == 42 and K.y.k == 23 &etc.)

1 Like

Thanks a lot for your answers. I still don’t really understand how this works, but it works :smiley: .

I will take the time to experiment with these concepts so to be sure I’m doing it the right way. I’m still really confused with the Object paradigm, which is a lot more complicated than I though at first. It’s usage feels different using several OOP languages and that is misleading.

I’m trying to develop a Quark and I would like it to be implemented correctly.

I’ll go a bit out on a limb, since I’m not sure exactly where the confusion is. But maybe this will illuminate a bit.

Maybe this is what I shouldn’t do : I’m trying to take advantage of the Class being a single Object that can hold datas.

There’s nothing wrong with this. The things are: how to initialize the classvars, and how to access them.

  • Initializing: *initClass is the right way. If a classvar will hold a simple literal value (integer, float, string, symbol), then you could do without initClass, but any objects have to be assigned within initClass.
GM {
	classvar <aFloat = 1.0;  // this is OK
	
	// this is not OK!
	// classvar <aColor = Color(0.5, 1, 0.2);
	
	classvar <aColor;
	
	*initClass {
		aColor = Color(0.5, 1, 0.2);
	}
}
  • Accessing: The < in <aColor has created a method *aColor { ^aColor } belonging to the class GM – not to instances.
GM.findMethod(\aColor)
-> nil

This is empty! Because it’s looking for an instance method, and there isn’t one.

Meta_GM.findMethod(\aColor)
-> Meta_GM:aColor

There it is.

This means, outside of the class GM, the only way to get to aColor is GM.aColor – to call aColor on the class itself.

Where it perhaps gets a little confusing is that instance methods defined within GM can use aColor directly. This is a matter of variable scope, not of any methods.

GM {
	classvar <aFloat = 1.0;  // this is OK

	// this is not OK!
	// classvar <aColor = Color(0.5, 1, 0.2);

	classvar <aColor;

	*initClass {
		aColor = Color(0.5, 1, 0.2);
	}

	okMethod {
		// direct access within a method:
		// aColor is within the {} scope where classvar aColor exists
		aColor.postln
	}

	notOkMethod {
		this  // access the instance
		.aColor  // this method doesn't exist for the instance!
		.postln
	}
}

Then:

a = GM.new;

a.okMethod
Color(0.5, 1, 0.2)
-> a GM

a.notOkMethod
^^ ERROR: Message 'aColor' not understood.
RECEIVER: a GM

In notOkMethod, it doesn’t matter that this is within the variable scope – this.aColor is asking to perform a method that doesn’t exist.

But if it were anotherOkMethod { this.class.aColor.postln }, then it would be fine – because this is replacing the instance with its class as the receiver of the message aColor.

So it’s necessary to distinguish between variable access (which depends on scoping rules) and message sending.

Instances

Jordan’s first example has a parent class with a classvar mainColor, and a subclass of it with its own classvar mainColor.

These are two variable declarations – so there are two variables… with the same name. This means GMUserVIew cannot access the parent’s variable through variable scoping (though it it can get it using GM.mainColor). My preference would be to avoid duplicating classvar names between parent and child classes.

The second example is fine, where the subclass has an instance variable (which users could change, per instance).

The super.new thing – for any new object to be created, it eventually has to go through Object *new or *newCopyArgs. These are, to my knowledge, the only methods in the entire class library that create the object instance – this means it is impossible for any instances of anything to exist, ever, without going through one of these methods! super.new pushes execution up through the class tree until it reaches Object.

The thing about super it isn’t obvious at first is that it dispatches the method call through the superclass, but within the superclass method, this is still the child class! This is necessary for Object to know how many slots to allocate. Then, this means that init at every level will call the child init method… maybe you don’t need to deal with this yet, just be aware of it for now.

hjh

1 Like

Oh, one more note – *initClass is superfluous here because you won’t be creating any instances of GMUserView until after class-tree init is finished.

hjh

I think the Gm.mainColour would better be called, default or starting colour.

However, this is might be unnecessary depending on use case, as the user view could just return…
^mainColour ? Color.red. There’s trade offs here, but it’s quite concise.

And this further raises the issue of how the class should function. To really get good object oriented code you shouldn’t think of class as collection of data (a struct if you will) but as having an interface that is used to do stuff to and with the object. For example, drawTheGUI, paintTheImage, changeColour. What you are doing right now is accessing and changing the colour of the object directly, implying you will be getting the colour elsewhere and using it, which is not always the best plan, particularly with a view, which should only have three types of methods, setData, updateASmallPeiceOfData, and drawGUI.

In short, think about how you want to USE the object, write those methods first, making sure their named something descriptive, usually containing a verb.

Feel free to post more code here, reviewing designs is quite fun, but you will get conflicting opinions…

Thank you very much for your detailed answers, they are invaluable for me, as I went way further into my Quark, and I think that the design is mostly right. I really hope you’ll find it useful.

@jordan, you are pointing a question I’m struggling with here : “good object oriented code […] shouldn’t think of class as collection of data”. Well this is what I understood, but then, if OOP is only Objects and nothing else, how do I provide a global and unique collection of data to the programmer, that classes can natively access ?

For now, I’m using three ‘levels’ of classes :

First the “GM” class, that inherits from Object. It acts like a “palette”, it’s instance variables contains the global visual specifications for other views. For example, if I first interpret GM.borderSize_(40), every view created afterwards will have a border size of 40. As such, I can’t see the reason why I should instantiate it at any time.

Then I have a GMUserView class, that inherits from UserView, and holds common method every of it’s subclasses should inherit (tautology :D).

Then, widgets themselves, that inherit from GMUserView and have specific methods.

Here, what I have now (truncated to the mechanism I don’t really get, so that it stays lisible, I can post the full code if needed). This is working, tho I don’t understand why I can’t init the GMUserView properly :

GM.sc :

GM {
	classvar
	<>mainColor,
	<>borderSize = 4;

	*new {
		super.new.init()
	}

	*initClass {
		mainColor = Color(0.75, 0, 0.333);
	}
}

GMUserView.sc

GMUserView : UserView {
	var thisBackgroundColor;
	var thisBorderSize;

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

	*initClass {
		Class.initClassTree(GM);
	}

	initThis {
		thisBackgroundColor = GM.mainColor;
		thisBorderSize = GM.borderSize;
	}

	borderSize {
		^thisBorderSize
	}

	borderSize_ { |aNumber|
		thisBorderSize = aNumber;
		this.refresh;
	}

	[etc]
}

GMSimpleButton.sc

GMSimpleButton : GMUserView {

	*new {
		^super.new.init;
	}

	init {
		super.initThis;
	}

	[etc]
}

Maybe can you see what I’m doing the wrong way.

You provide an interface (a set of methods) that returns the data to the (any) caller.

The caller doesn’t go in and get the data.

The caller politely asks the object for a specific datum and the object replies.

The object can store its data in any way that it likes – the caller has no business knowing that, specifically. The object goes to into its own closet, gets out the thing you’re asking for, and gives it to you.

In SC, everything is done by asking nicely.

hjh

Since a Class is itself an Object, is it not what’s happening when the new GMUserView instance is asking for, let’s say, GM.borderSize ?

If I understand this correctly, just after compile time, the only Objects that have been instantiated are Classes, and I guess internal SuperCollider utilities (default server, top environment, etc). Since I don’t think a Quark should create such internals Objects, I guess I’m only left with Classes instances to ask politely, right ?

In other words, what if the first action I do after compile time is to instantiate a custom view, like the previously mentioned GMSimpleButton. Where will it find the right borderSize ? Is it supposed to be inherited from one of its parent only? Or is it legit to access it from another class class variable like I did? Or should I create an instance from this class, and set it as the object to ask to politely for every subsequent view created?

No, you can create what you need.

Definitely not restricted to inheritance! Actually inheritance is often the wrong idea (but it’s the first one that many programmers reach for).

If you need storage for parameters, then make a class that holds the parameters. Other objects can request freely from this object, provided that the storage object provides methods for that.

In general it’s better to ask for stuff from methods, rather than to grant access to objects’ internals.

hjh

1 Like

Thank you for those detailed clarifications, and sorry if my questions were messy. I think I will need some time to integrate those concepts correctly. I’ll read this topic slowly again during the week.

For now everything is working, and the code looks like most of other .sc files, so I think I’m taking the right direction.

Regards,
Simon

Thinking further about it: if the idea is a “view preset” or palette, I’d suggest to store the specific colors in instances.

If the colors are held in classvars, then you’re forever limited to one and only one. You might someday not be happy with that.

If they are held in instance vars, then you could provide a library of presets from which the user could choose (or the user could add their own). More flexibility is generally a better idea.

GM {
    classvar <all;
    classvar <>default;

    var <>background, <>foreground ... etc;

    *initClass {
        all = (
            light: this.new(Color.grey(0.8), Color.black),
            dark: this.new(Color.grey(0.2), Color.grey(0.9))  ... etc
        );
        default = all[\light];
    }

    *new { |background, foreground|
        ^super.newCopyArgs(background, foreground)
    }
}

Then your view classes could accept an instance of this class at creation time. If the user didn’t give an instance, use GM.default.

I’d also suggest that the views provide an applyViewProperties method, with an argument that is the instance of GM. Then the user can change the display just by passing in a different instance.

hjh

1 Like

Thanks for the suggestion. I need to think deeper about it until I decide how to deal with this idea of palette. Currently, with a naive approach, I’m satisfied with the results it gives. Since the color is copied instead of being a reference when the view is instantiated, I can have different styles in the same window :

The following

var button, button2;
button = GMSimpleButton();
GM.borderSize_(20);
button2 = GMSimpleButton();

results in two buttons having different border sizes. Then we can have a simple function that changes GM values between views creations.

This is not implemented yet, but I think that what will be missing afterwards, is a method for each view to reconfigure it’s variables automatically according the current GM values.

Since understanding and manipulating .sc files is new to me, I think my code isn’t really congruent with the native View ‘style’ of creating graphic components, and furthermore almost unrelated to the QT implementation, but I will take the time to go deeper into understanding these, and I don’t mind taking the time to rewrite any code that could be written better.

Hey,
so I actually don’t think you should be using classvars for this.

Generally, classvars (global properties) should be constant. This is the modern programming consensus. Now supercollider is old and uses them throughout, but that doesn’t mean you should!

Instead I’d recommend something like this inspired by Qt…

GMProperty {
	var <value, listenerSlots;
	*new {|v| ^super.newCopyArgs(v); }
	connectToUpdate{
		|l|
		listenerSlots = (listenerSlots ? []) ++ [l].flat;
		[l].flat.do{|f| f.(value)};
	}
	updateValue { |v|
		value = v;
		listenerSlots.do{|l| l.(v) }
	}
}

GMStyle {
	var <mainColour, <secondaryColor;

	*new {^super.new.init}
	init {
		mainColour = GMProperty(Color.red);
		secondaryColor = GMProperty(Color.yellow)
	}
}


GMButton {
	var <but; //access only for demo
	*new {|style|
		if(style.isKindOf(GMStyle).not,
			{^"GMButton.new: expected GMStyle".error});
		^super.new.init(style)
	}
	init {|s|
		but = Button().states_([
			["yes", Color.grey, Color.white],
			["no", Color.white, Color.grey]]);
		s.mainColour.connectToUpdate({|v| but.states[0][1] = v });
	} 
}

Whats happening is that each property has an array of functions to call each time it updates, and individual objects can register any function to be called on said update.

~myStyle = GMStyle();

~myBut = GMButton(~myStyle    /*  you should also pass in some data to model here */);

~myBut.but.states[0][1] // the default value

~myStyle.mainColour.updateValue(Color(0.3, 1.0, 0.2))

~myBut.but.states[0][1] // automatically got updated as it was connected to ~myStyle's mainColour

This way, there is no global state at all! Now if you wanted to save a default style in a classvar, that would be okay, so long as you don’t change it, only use it to create instances that you then specialise.

The problem with global states is that its incredible hard to keep track of them at scale… also they make multithreading hard, not a problem in supercollider, but it is one reason why we don’t have proper multithreading (just take a look at how synthdefs are made … eeeek)

2 Likes

Thank you, I get it better!

Generally, classvars (global properties) should be constant.

It breaks my tiny heart, but so be it ! I don’t know much about low level programming but I think I can imagine how it makes the underlying mechanism of a software much more unstable.

~myBut = GMButton(~myStyle

This is what I wanted to avoid, so that once the programmer has defined a style, it stays activated, without further calling (and thus further typing). But I this is unfeasible without using setters on classvars.

I need to think about this a bit further because it introduces some tricky workarounds.

I meant, you do what you want, sometimes breaking these guides is necessary, but that will come at a cost. Its generally constant, not always.

For example, the UGen class has a classvar that stores the graph that’s currently being built and all the UGen subclasses append themselves to that. The pitfalls of this mutable classvar are mitigated by wrapping it up in a function, so you never change it directly.

e.g…

GMProperty {
	var <value, listenerSlots;
	*new {|v| ^super.newCopyArgs(v); }
	connectToUpdate{
		|l|
		listenerSlots = (listenerSlots ? []) ++ [l].flat;
		[l].flat.do{|f| f.(value)};
	}
	updateValue { |v|
		value = v;
		listenerSlots.do{|l| l.(v) }
	}
}

GMStyle {
	classvar <>currentActiveStyle; // this is new
	
	var <mainColour, <secondaryColor;
	*new {
		|mainColour, secondaryColour|
		^super.new.init(mainColour, secondaryColour);
	}
	init {
		|m, s|
		mainColour = GMProperty(m ? Color.red);
		secondaryColor = GMProperty(s ? Color.yellow);
		^this
	}
	withStyle { // and this is new
		|f|
		currentActiveStyle = this;
		f.();
		currentActiveStyle = nil;
		^this
	}
}


GMButton {
	var <but;
	*new {|style|
		^if(style.isNil,
			{super.new.setStyle(GMStyle.currentActiveStyle)},
			{super.new.setStyle(style)})
	}
	setStyle {
		|s|
		but = Button().states_([
			["yes", Color.grey, Color.white],
			["no", Color.white, Color.grey]]);
		s.mainColour.connectToUpdate({|v| but.states[0][1] = v });
	}
}

Here accessing the classvar is supposed to be guarded and used through the withStyle function. But if this syntax is still lacking then you are forced to go ahead and use the classvar directly, just be aware if a user puts some non GMStyle variable in here you have absolutely no control over what will happen, unless you write a bunch of error checks.

~mainStyle = GMStyle(
	mainColour: Color.red, 
	secondaryColour: Color.yellow
);

~mainStyle.withStyle({
	~myBut = GMButton();
});


~myBut.but.states[0][1] // the default value

~mainStyle.mainColour.updateValue(Color(0.3, 1.0, 0.2))

~myBut.but.states[0][1] // automatically got updated as it was connected to ~myStyle's mainColour