Extension Conditionals for Interoperability

Hi all! I’ve been trying to come up with a way to utilize extensions in a script and make the script shareable without forcing anyone who downloads the script to download the same set of extensions that I happen to like and use. I’ve managed to write a conditional that works to check for the presence or absence of a given extension (i.e., it returns true if the extension is present and false if the extension is absent) and runs certain functions that utilize that extension only if the extension is present. But when I removed the extension from my extensions folder, I realized that the script still wouldn’t run!

Have others done anything like this? I did some searching and didn’t see any questions quite like this — maybe it’s not a super common use case. In my case, I’m trying to make a script that’ll utilize a monome grid if you’ve got one but will pop up a gui grid if you don’t. And I’ve got that working, but only if the monomeSC extension (for using monome grid with SuperCollider) is present. Here’s a gist of the WIP script, to give a sense of what I mean. The conditional extension stuff is happening in the first 100 lines. It doesn’t seem sensible to have people who don’t have grids downloading the monomeSC extension just to run this script. I’m stumped and would very much appreciate any guidance anyone has to offer!

Both blocks to if are compiled, i.e.

false.if { undefined } { 'answer' }

is an error. Perhaps see String>>interpret (or String>>load &etc.), i.e.

Class.allClasses.detect { | each | each.name == 'Optional' }.isNil.not.if {
	"Optional is installed!".postln;
	"Optional.init".interpret
};
1 Like

.asClass is also useful for this case.

'SinOsc'.asClass
-> SinOsc (the class)

'Undefined'.asClass
-> nil (I think)

// trying to use an undefined class in interactive code
Undefined

(Produces a parse error)

hjh

1 Like

Its not that people don’t want to do this sort of thing. Its that supercollider has exceptionally poor support for such things. What we need is a proper module system.

Some issues you might run into:

  • Make sure you actually remove the quarks to test the code, since class files can arbitrarily extend classes adding methods using the +Class {} syntax, it is very easy to use code from a quark without realising it.
  • For writing the code, I would recommend you make a new file for each option and use String.load.

It might be easier to use a try and catch block instead, where you default to a GUI approach, rather than offer one.

1 Like

hear hear! I have been struggling with a similar problem - gave up migrating my setup to an interns machine after 3 hours of noodling. Very frustrating

1 Like

Thanks so much for these responses, everyone!

this basically works! By encasing all the stuff that uses the monomeSC extension in quotes and adding .interpret to the end, I’ve gotten the script running successfully with or without the extension installed. Without the extension, I get errors in the REPL about the lines containing monomeSC stuff (i.e., it seems like this is still getting interpreted whether the if condition is met or not, as @rdd mentioned), but the script will still run, and some preliminary testing didn’t indicate any issues with any of its functionality. Getting errors in the REPL is not ideal, of course, but at least the thing’s running with or without the extension, and of course, adding the monomeSC extension back into the mix gets the errors to go away. I’ve updated the gist I linked to in the op above with this interpret-based solution implemented. It’d be neat if there was a way to tell sc not to interpret the string if the condition is not met. I wonder if

would stop those REPL errors from being thrown? It seems like it wouldn’t, since the basic issue is that both conditions of the if statement are being evaluated (i.e., here it would evaluate the if, but it would load the string, or at least see what happens if it loads the string, whether or not the if condition for loading the string is met). To clarify, though: for String.load, would I save the separate file as a .scd, a .sc, or a .txt, or does that even matter in this case?

as it stands, this is kind of what the thing defaults to. At least, there is a gui, whether a grid is present or not. But I’m curious about

and reading a bit about it in the help docs now, under “Exception,” here. I don’t really get how I would go about writing something like this yet, but maybe if I spend some more time with the help docs, I will. If there are any other helpful resources of examples regarding catch block syntax in sclang, please let me know!

It’d be neat if there was a way to tell sc not to interpret the string if the condition is not met.

An ordinary if message will do that, i.e.

false.if { "undefined".interpret } { true }

In the link above you have the quotes on the outside of the braces.

If you don’t want to inline the text you can load a file conditionally, i.e:

'MonomeGrid'.asClass.isNil.not.if {
	"setupMonome.scd".loadRelative
}

Ps. Sc implements value at Object so the if arguments can be any object, i.e.

false.if(0, 1) == 1

Writing if expressions using trailing closure syntax can help with error messages:

false.if { 0 } { 1 } == 1
false.if 0 1 // error
1 Like

oh, good catch! I’m still getting the same error messaging if I put the quotes and the .interpret inside the braces, though, so it seems like it’s still interpreting the string. For clarity, I updated the gist again, and this is the error I’m getting if I remove the monomeSC extension (i.e., if the I render the condition of the if false):

ERROR: Class not defined.
in interpreted text
line 4 char 14:

			MonomeGrid.getConnectedDevices.size > 0,
            
			{

My interpretation of this is that it’s interpreting the monomeSC stuff in the string regardless of the truth or falsehood of the if condition.

My interpretation of this is that it’s interpreting the monomeSC stuff in the string regardless of the truth or falsehood of the if condition.

Or perhaps the condition is somehow true? The if message really ought to work, it’s kind of important!

false.if { "undefined".interpret } == nil
true.if { "undefined".interpret } // error

You can use 'AClassNameThatMightNotBeDefined'.asClass to avoid this problem (mentioned earlier but overlooked).

E.g.,

'MonomeSomething'.asClass.new( ... )

hjh

1 Like

using asClass still throws an error for me, but it’s a different error. In this case, for example, MonomeGrid.asClass.getConnectedDevices.size > 0, which should return false if the monomeSC extension is present and no grid is connected, true if the monomeSC extension is present and one or more grids are connected, and shouldn’t be interpreted at all if the monomeSC extension is not present, instead returns ERROR: Message 'getConnectedDevices' not understood. if the monomeSC extension is not present. Changing it to 'MonomeGrid'.asClass.getConnectedDevices.size > 0 yields the same result. So does MonomeGrid.asClass.getConnectedDevices.asClass.size > 0. getConnectedDevices is, of course, something that’s defined in the monomeSC extension. I’m still not sure how to get around that.

I’m almost entirely certain that the condition isn’t true. Evaluating the condition on its own, outside the context of the if, returns true in the REPL if the monomeSC library is present and false if the monomeSC library is absent.

I’d do it like this:

var gridClass = 'MonomeGrid'.asClass;

// now "gridClass" is either the class, or nil

... do other stuff ....

if(gridClass.notNil and: {
    gridClass.getConnectedDevices.size > 0
}) {
    // this part runs only if MonomeGrid exists
    // and it found connected devices
};

EDIT: Perhaps better, if you need access to the device list:

var gridClass = 'MonomeGrid'.asClass;
var gridDevices;

// now "gridClass" is either the class, or nil

... do other stuff ....

if(gridClass.notNil) {
	gridDevices = gridClass.getConnectedDevices;
	if(gridDevices.size > 0) {
		// this part runs only if MonomeGrid exists
		// and it found connected devices
	}
};

(It may be possible to write the latter in a tighter way, but I don’t think it can be written in a clearer way.)

hjh

1 Like

after some finagling (including using ~ instead of var for both gridClass and gridDevices because I kept getting errors involving var being “unexpected,” seemingly no matter where I put it), this is working! I’ve updated the gist linked above to show the successful implementation. All 4 possible states are being handled correctly:

  1. monomeSC library absent, grid absent → use GUI grid
  2. monomeSC library absent, grid present → use GUI grid
  3. monomeSC library present, grid absent → use GUI grid
  4. monomeSC library present, grid present → use physical grid, don’t display GUI grid

and no errors are being thrown on execution in any of these 4 states!

I do find it weird that if statements sometimes seem to like being formatted as
if(condition) {trueFunc} {falseFunc};
and sometimes seem to like being formatted as
if(condition, {trueFunc}, {falseFunc});

Both of these seem to work. Sometimes one seems to work when the other doesn’t. Right now, I’m using a mix of both of these formats, which doesn’t seem great. If anyone can shed light on the situation, I’d appreciate it. But in the meantime, I’m just thrilled that extension conditionals are working in my script without throwing errors! Thank again, everyone.

Excellent news!

One way of looking at things is…

SuperCollider language is in the Smalltalk family from which it inherits a very simple and elegant semantics.

The semantics is sometimes summarised as “everything is an object and all you can do is send a message”.

A message consists of a “selector” and zero or more “arguments”.

Sc also has quite a lot of syntax!

Still, (almost) everything can be written as either:

  • receiver.selector
  • receiver binarySelector argument
  • receiver.selector(commaSeparatedArgumentsList…)

Conditionals are just ordinary messages. (They have to be because there’s nothing else!)

To delay evaulation the branches are written as no argument functions (sometimes called blocks).

{True,False}>>if just sends a “value” message to the appropriate branch and ignores the other.

One of the nicer bits of Sc syntax is that “trailing closures” can be moved outside of the parenthesised argument list if they’re literals.

Another bit of (case-sensitive) syntax is that:

  • f(x) → x.f
  • f(x, y) → x.f(y) &etc.

Putting these together you get:

  • if(x) { whenTrue } { whenFalse } → x.if({ whenTrue }, { whenFalse })

Because Object>>value is the identity the arguments to if don’t have to be functions. (This “value” protocol is quite convenient but can cause a little bit of confusion…)

Ps. Case sensitive because:

  • F(x) → F.new(x)
  • F(x, y) → F.new(x, y) &etc.
2 Likes