Using 'includes' on an Array of Strings

I just ran into the following unexpected situation:

[\aif, \aiff, \wav].includes(\aiff); //returns true
["aif", "aiff", "wav"].includes("aiff"); //returns false

And I have no idea why. Can anyone explain why the second expression doesn’t return true? Is it related to the fact that Strings are themselves a type of Collection?

Eli

I’m afk but i think it’s because the symbols are identical, while the strings are merely equivalent.

One way around it is

[…].any(_ == “aiff”)

(again, on my phone, can’t be sure)

Cheers,
eddi
https://alln4tural.bandcamp.com

Yes, includes checks for identity. If you want to check strings (where two identical strings can be different objects) you can use includesEqual.

Ah, that makes sense. Same reason for this difference:

\aiff === \aiff; //true
"aiff" === "aiff"; //false

I did not realize two identical symbols were considered to be the exact same object. I guess they’re sort of like numbers in this sense?

Thanks!
Eli

that’s the reason for symbols as I understand it.

faster to use for lookups as opposed to string comparisons

I believe this is due to there only being one instance (memory location) for each symbol as they are stored in a hash map (I think), but strings are made anew every time so have a unique memory address for all instances. In C like languages this is the difference between comparing pointers (or references) and values.

Just never store a string, only use them to build a symbol ( "S1"+ "S2").asSymbol.

This is one of may least favourite “features” of sclang, it makes symbol comparisons blazingly fast, but syntactically painful when doing things like this. Since there is no type programming, it becomes even more precarious when you try to do prototyping with environments — particularly when you can’t count on the format of the string, it might be an osc address or something with underscores. When env.use{ ~var } != env["var"] != env.var, or worse, env["/osc/addr".asSymbol] … Here lies dragons.

J

I agree it can be frustrating. But in a way, I think SuperCollider is one of the few languages that actually gets this subtlety mostly-right?

Many languages (javascript, lua, etc) only have mutable strings, the equivalent to sclang’s String. There is no way to do constant-time comparison or lookup of strings, no efficient maps with strings, etc.

Some languages (Python for example) only have immutable strings, the equivalent to Symbol - so you get fast comparisons and fast string-based dictionaries, but doing operations on string ("a" + "b") are very slow.

Ruby combines immutable and mutable strings into a single type / object. This is beneficial in some ways (it avoids different "string" 'literal' syntaxes…), but it also can make it a little more ambiguous to know what you’re dealing with, and means making an immutable string literal has to look something like "stringLiteral".freeze() (not so familiar with Ruby, but it’s something like this).

For me, it’s a toss-up whether the SC or Ruby implementations are better, but I certainly prefer being able to use syntax like registerSynth(\mySynth, node) rather than registerSynth("mySynth".freeze, node).

My general advice is to use Symbols everywhere, unless it’s for display purposes or you’re constructing the string at runtime (e.g. "synth_name: %".format(name)).

Here’s an experiment for someone to try:

+String {
    === {
       |rhs|
       ^(this == rhs)
    }
}

This should make identity comparisons the same between Symbols and Strings. This will definitely make things slower (how much slower?) and may blow something up, but probably a fun thing to try anyway.
Note that this won’t change the behavior of IdentityDictionary, because the lookup is hard-coded in C to compare identity.

Out of curiosity, could anyone offer a { }.bench example to demonstrate just how drastic this speed difference is?