SuperCollider 4: First Thoughts

One issue here is that there is not only one kind of type conversion.

If I understand your point correctly, you’re proposing that there is a category of operations called “type conversion” and that all members of this category should behave the same – to homogenize.

But I think the category “type conversion” is itself heterogeneous. For instance, type conversion may be divided into “if-necessary” conversion (where the conversion method prefers to return the receiver if that’s appropriate) and “forced” conversion (always create and return a new object based on the receiver).

In general, method names of the format asSomething are if-necessary, while as(Something) is “forced” (since it’s a synonym for Something.newFrom(x)).

The argument seems to be that the two syntaxes asSomething and as(Something) are very similar, so they should be unified. But they mean different things. You can’t unify operations that are not the same.

A possible solution here would be to remove as(Something) and require users always to write Something.newFrom(x), and reserve asXXX for the if-necessary case – that is, to make the syntax more divergent for different operations. This also makes the problem with as(Integer) explicit – because there is no concept of a “new” Integer (or any atomic type). I guess the best you could do is:

+ Integer {
	*newFrom { |x| ^x.asInteger }
}

… but I can easily imagine someone reading this and thinking “what-the-xxxx is that for?” … and there would be no point anyway to writing Integer.newFrom(aNumber) vs aNumber.asInteger.

I think this can be explained as a case of multichannel expansion. Many places in SC accept either a single number or an array of numbers (multichannel expansion), so it isn’t completely outrageous for numeric type conversion to multichannel-expand also.

Consider this:

f = { |numberOrArrayOfNumbers|
	// now I want all of them to be floats
	if(numberOrArrayOfNumbers.isArray) {
		numberOrArrayOfNumbers = numberOrArrayOfNumbers.collect(_.asFloat);
	} {
		numberOrArrayOfNumbers = numberOrArrayOfNumbers.asFloat;
	};
	... etc...
};

I don’t think anybody really wants to be forced to do that… but if asFloat didn’t multichannel expand, this is what everyone would have to do.

Fully agreed – Array2D is not a useful class. We should deprecate it.

It’s a worthy goal to address sources of confusion. But, I’m skeptical of the idea that a confusing area in a programming language is necessarily the fault of the language. (For instance, multichannel expansion of numeric type conversion is a little confusing, but it’s also very useful! And, consistent with other idioms in SC.)

Programming language acquisition mirrors natural language acquisition. A child learning English might say “dad goed to the store” and then find out that the general rule “-ed = past tense” doesn’t apply to all verbs. Similarly, when encountering asArray and as(Array), it’s understandable to overgeneralize and collapse them into one category – but they don’t actually belong to one category. This process of overgeneralizing and then refining distinctions is a natural part of learning programming. (The opposite process – undergeneralizing and gradually coming to understand a more general principle – is another natural part of it.)

There is a temptation to say “if I have to refine my understanding, then something is badly designed in the language” but this may not always be true (or, redesigning might introduce different confusion). Lately I notice this kind of thinking in myself (“I’ve been doing this a long time, I know what I’m doing, I shouldn’t suffer confusion so it’s the software’s fault” :laughing: ) and I’m working at recognizing it and questioning whether design could really have avoided confusion.

With that said, it might actually be a good idea to get rid of as(aClass) – another case of syntax sugar sometimes backfiring.

hjh

4 Likes

Can you explain this inconsistency as noted by @fmiramar?

[1.0, 1.0].asInteger // works
[$a, $b].asInteger   // throws error
$a.asInteger         // -> 97

I’m kind of stumped, to be honest.

Edit:

$a.class.findRespondingMethodFor('asInteger') // -> nil

So why doesn’t $a.asInteger complain?

asInteger on a SequentialCollection is basically implemented as
^this.collect({ arg item; item.perform(\asInteger) });

For some reason, $a.asInteger works, but $a.perform(\asInteger) doesn’t. Don’t know why, but it’s certainly a bug.

Typical methodology for this type of question is to check the byte codes:

{ $a.asInteger }.def.dumpByteCodes

BYTECODES: (4)
  0   40       PushLiteral Character 97 'a'
  1   B0       TailCallReturnFromFunction
  2   D7       SendSpecialUnaryArithMsg 'asInteger'
  3   F2       BlockReturn

At this point, then, the short answer is that SendSpecialUnaryArithMsg handles some types internally in the C++ function, and falls back to a normal method call only if the type isn’t handled. (The longer answer is that opcode 0xD7 = 215 calls handleSendSpecialUnaryArithMsg() and this calls doSpecialUnaryArithMsg(), and here there is a long section for case tagChar:.)

When you .perform(\asInteger), it’s bypassing the special unary arithmetic message and looking for a method to implement.

But my other question about this is, why use .asInteger when a more canonical way to get the ASCII code of the character would be .ascii? (That is, it may not be good that there is an inconsistency about .asInteger, but I’m not sure we can exactly call it correct usage either. Also nobody found this for almost two decades… agreed that this is a rough edge, but it’s also a rough edge that is not sticking into anyone’s eye.)

hjh

2 Likes

Thank you for the explanation!

Definitely agree, I was just curious about how one would go about figuring out the source of the behaviour - what kind of debugging strategies exist for more low-level things like this, basically. I’ve used dumpByteCodes before, but didn’t think of using it here, and I didn’t know how to connect the dots between the byte code output and where exactly in the C++ source these things are handled. Your post led me to PyrInterpreter3.cpp, so that seems at least a bit more clear to me now!

Very little of this is documented – here, I was proceeding on experience (realizing that the operation could be done by a primitive called from a class library method definition, or by the C++ implementation of the opcode, and no other way – and we know in this case that it couldn’t be the former, so it must be the latter).

I guess that’s not a very forward-looking answer but documenting all of that is a massive job and other issues are more pressing.

hjh

the .asArray vs .as(Array) is also about having too similar names.

To illustrate analogous confusions with a funny history: a friend of mine spent hours trying to find a radians converter on SuperCollider because he thought that .degrad was a bitcrusher/decimator :rofl:

I know this can be too much about personal preference, but .degrad and .raddeg wouldn’t be more clear names if swapped for .degrees and .radians ? I know that SC conversions methods are named in this style, but this .degrad name resembles MAX metaphorical naming “convention” which I find extremely confusing…

That’s quite an understatement. The in-code GUI graphs that borrow a bit from what some editors like VSCode do with its inline thingies (like for git), is quite revolutionary, IMHO. Unfortunately on Windows, Gibber puts the DWM in some mode in which I can’t take screenshots, meaning they come out all black, or I would have pasted on here…

1 Like

yep love those in-code animations - could add interest to livecoding for sure

I wonder if something like that could be hacked together for those of us using nvim …

You mean 1-bit like on old home computers or like in Direct Stream Digital Audio (DSD)?

Python is an example of a very permissive language for types, and more recently included the option of declaring types with mypy. In the case of Python, there is an advantage in both performance and correcting errors, but it doesn’t change anything fundamental. I think SC could do something like this too. See: https://www.mypy-lang.org/

But there is a massive difference between Python+mypy and a functional language like Haskell, where the type system is a much deeper concept in the language. Just take a look and you will realize that.

Writing code in a dynamic language with a good type system and good tooling (I’m thinking specifically of Python and Typescript) is a fantastic experience, much smoother, easier, and less error-prone than with no types.

I’ve been digging around pretty deeply in type system implementations lately for another project, so I have some pretty clear ideas about how it might be done in SC. But doing this in a way that’s usable is pretty difficult, and if it has holes or incomplete implementations it can make the programming experience a bit clunky. You can really quickly find places where even Python’s type system falls apart, or where the Typescript type checker simply stops having any idea what you’re doing.

There’s a lot of work I would probably put in front of a type system - I’d say the entire parse-compile pipeline should be updated before even starting something like this - but it’s a worthy project. Probably it would only ever get done if it was someone’s computer science PhD, since it’s likely a 6-9 month effort at least and a PhD program is the only thing that would ever pay for something like that :slight_smile: - so, you know, lets start encouraging some comp sci PhD’s.

2 Likes

I imagine the amount of work that must be something of this nature. I found this discussion interesting precisely because of its ambition. A few years ago, any comment about sclang would have the answer: “Learn another language; it’s not that difficult.”

2 Likes

So, basically, turn sclang to everything we know and love about C++.
This alternative view has a really bad prognosis. It breaks ergonomics in a major way.

I think sclang should be modernized. And that could definitely involve a lot of pruning. And a thorough rethinking of how to unclutter the whole language and setup some guidelines to make it and keep it consistent. You should be able to infer the behaviour from the language grammar rules, without recourse to manuals to see if an alternative (read: mostly superfluous) way to spell something is “better” or more idiomatic or has some undeducible irreducible quirk.

Sclang is just thoroughly unpleasant to handle and its uneven and bloated nature plays a big part of this. There’s nothing even charming about it.

1 Like

thoroughly? Agree to disagree. .

Lukiss.

1 Like

In comparison to how modern languages feel? Yes, yes, quite thoroughly. I’m not happy to admit that it feels worse than Javascript.

I’m adamantly of the opinion that any unexpected, undeducible, and implicit behaviour is the worst thing to a programming language. A No.1 priority for a design of a language: a reader can quickly and correctly read the intention and effects of an expression.

There’s a bunch of questions raised in this thread which seem to me to be based on a design of a language which is ultimately confused as to what it wants to be.

Moreover, a programming language is not just a tool of function – it’s also an aesthetic. It’s a means of expression, not mere communication, and thus it doesn’t lack a deep poetic element that pervades every letter of the language. It’s an element through which you may channel your intentions – it’s the shape you provide to your thoughts and feelings. I can’t imagine the sort of treatment one must have undergone to be allured by the syntactic superfluities and irregularities of sclang.

Don’t get me wrong – SC is really a monumental achievement. But its laurels belong firmly in the hands of the server, not the lang.

Indeed. I’m just stating my impressions. I’m a crybaby for these sorts of things. Your experience does not need to coincide.

Yes, You sort of come across like a crybaby.

Just curious. If it really is that thoroughly unpleasant for you and you cannot find anything charming about it, are you sure this is for you?

Sort of like your prose :slight_smile: The strength with sc-lang, just as spoken language, is that you don’t really have to use toss in all the the expensive words and preamble in order to just state it plainly.
Find your own way to write the poetry. Your way.

I find mad poetry in the #supercollider tweets. After more than 20 years of dabbling in sc-code every now and then I still, weekly, learn and find new unexpected poetic and beautiful quirks and ways to express things in this language.

There is Overtone, FoxDot and other things that might be for you if you want to use the server.

mkaythanksbye.
I will let myself out now.

Lukiss.

PS. I wish the documentation for the language was… more welcoming.

Oof, a bitter one, aren’t you. Sorry for hurting you, I really had no intention to attack anyone with that bit of humour, maybe it was misplaced. I tried to emphasize that we may well diverge in our impressions, it need not cause resentment.
Peace.

If sclang is stuck in 90s-00s style object orientation, then… it’s also true that no other language client has emerged to supplant sclang.

Other language clients exist, but none of them has gained enough traction so that, for instance, one could reasonably answer complaints about sclang by pointing to that other client.

It’s worth considering why that might be.

Some reasons would be practical. To make a comprehensive replacement for sclang is a huge job (that is, easy to start, but hard to get around to all the yummy stuff). Documenting it is an even bigger job.

But I think sclang has some features that facilitate creative use, which may be less convenient to replicate in other environments.

To take just one example (at the top of my list): function/method signature overloading.

f = { |note| note.degreeToKey(Scale.major) };

f.value(0);
-> 0.0

f.value([0, 2, 4]);
-> [ 0.0, 4.0, 7.0 ]

A trivial example, but one which points out that sclang can handle single pitch values and pitch arrays (chords) interchangeably with no extra demands on the user. I think any sclang-killer needs to be able to overload at least this transparently. If you’re a composer or producer or improviser and you have to use advanced templating syntax to get this result, it’s going to slow you down. So, for instance, where maldu criticizes “a design of a language which is ultimately confused as to what it wants to be,” it’s equally plausible that the original choice of runtime dispatch was deliberate, in order to facilitate exactly the use case above, which is of critical importance to musicians and this is a tool for musicians (and sonic artists, and scientists, but musicians are way up there on that list). If there are more modern language technologies for this, that’s great! Then… write up a proposal.

It’s useful, up to a point, to identify shortcomings in sclang. It would be more useful to point toward improvements. If that improvement would be for the SC environment to switch to a different language with a larger support team, then… which language? And make a plan to implement a usable feature set in that language. If the improvement is to keep sclang and address shortcomings, then, be specific about the shortcomings and propose solutions. “Somebody oughta” doesn’t get the job done.

hjh

10 Likes

Lol lets keep things civil plz :slight_smile:

This is a bit of a dead horse I trot out from time to time, but… the most core, important functionality in sclang is built on top of a VERY feature rich language with a handful of very specific syntax choices. The SynthDef DSL, the pattern and event systems, array operations, functional programming techniques like composition, heavy use of lambdas / closures, predictable-pause garbage collection - ALL require this feature set. There are no other mainstream languages that have the right features to enable these things - at best, you’ll get about 75% of the right things and with more frustrating syntax. As much as sclang is a hot mess (it definitely is), the nearest contender in terms of functionality is Lua - and for anyone that hasn’t been around the Lua ecosystem lately, it’s significantly more of a hot mess than sclang. :slight_smile:

Sad fact is: the options are either commit to sclang, or rewrite portions of the class library in another language with the knowledge that you’d be trading up on tooling/support/documentation/etc. but at the same time making some portion of the existing class library functionality impossible or inordinately hard. Both are reasonable paths depending on what kind of code you’re excited about writing (or NOT writing) and what specific kinds of cognitive load you want to deal with as a prerequisite for your music practice.

I’d love it if e.g. sclang has type checking - it would a huge improvement for MY music practice, and probably great for the ecosystem, usability, etc. But in all honestly the engineer-hours to build and maintain even a modest type system would arguably be better spend in other ways, and a poorly implemented / poorly maintained type system is probably worse than none at all. Doesn’t mean this is a bad project, but neither is it a magic bullet.

5 Likes