Support

If you have a problem or need to report a bug please email : support@dsprobotics.com

There are 3 sections to this support area:

DOWNLOADS: access to product manuals, support files and drivers

HELP & INFORMATION: tutorials and example files for learning or finding pre-made modules for your projects

USER FORUMS: meet with other users and exchange ideas, you can also get help and assistance here

NEW REGISTRATIONS - please contact us if you wish to register on the forum

TRUE or FALSE?: Ruby booleans are weird!

For general discussion related FlowStone

TRUE or FALSE?: Ruby booleans are weird!

Postby trogluddite » Sat Dec 19, 2020 12:48 am

Just recently, the following Ruby problem was posted...
This code also shows no errors when using midi splitter and combiner:

Code: Select all
if @status = 144 and @note = 48 or 50 or 60
    # [...]
end

The intent of that code seems pretty obvious. But despite there being no error messages, it doesn't do what it appears to: The if predicate is always TRUE and, even weirder, the @status and/or @note variables may get unwanted new values assigned to them!

There are multiple issues contributing to the unexpected results, so no quick way to describe why it's that way (I did quickly respond with a fixed line of code, of course!) Hence, I made this thread to discuss a bit more about how Ruby "booleans" and predicates ("if" being the most common) work, taking the above code as an example.
(NB: Watch out for example code boxes that need scrolling)

1) Like most computer languages, Ruby allows the boolean values (actually objects) true and false. These work much like you would expect if you're familiar with "green" boolean values, and you can write them directly into your code...
Code: Select all
x = true
y = false
z = x || y   # Can be written: z = x or y
# z now equals true.

NB: These Ruby objects are always written in lower case. I'll try to use bold text for Ruby stuff in the text, and CAPS when I just mean the abstract concepts from boolean logic.

2) However, all Ruby objects have a boolean TRUE/FALSE equivalence. The objects false and nil are equivalent to boolean FALSE. All other Ruby objects, even just simple numbers, are equivalent to boolean TRUE.
Code: Select all
if false
   # This won't run
end

if nil
  # This won't run
end

if true
  # This runs
end

if 3.1415927
  # This runs
end

if "hello"
  # This runs
end

# etc....


3) To test for equality, we must use double-equals (==). In Ruby, single equals (=) always assigns a value to a variable or to an attribute of an object. However, just to complicate things, assignments in Ruby also count as expressions - that is, the assigned value is returned to the surrounding code as a "result" of the operation...
Code: Select all
# This assigns 10 to x. It also sends 10 (the "result" of the assignment) to the output.
output (x = 10)

# This assigns 10 to x. The result of this is then assigned to y; so y is also now 10.
y = (x = 10)

# This is same as above; the "=" always binds closest to the variable on its left.
y = x = 10

# And snipped from the example code...
if @status = 144
    # This assigns 144 to @status, then passes the result 144 to the "if".
    # Since 144 is equivalent to TRUE, these lines will now always execute.
end


4) The "symbolic" operator ! (=NOT) has priority over && (=AND), which has priority over the || (=OR) operator (the same way that multiply/divide have priority over add/subtract in maths). However, the "English language" not, and, and or operators don't do this - they always read strictly from left to right. This means that these two styles are not always interchangeable without changing the meaning of the expression...
Code: Select all
# This means: (a AND b) OR (c AND d)
if a && b || c && d

# This means: ((a AND b) OR c) AND d
if a and b or c and d


5) This is probably the weirdest one. Given what I've said above, what would be the result of "50 and 60" (or "50 && 60")? The (Ruby object) true, right? Nope, the result is actually 60! In fact, the result is always one of the two operands (whichever determines the final answer; the right-hand side isn't even evaluated if the left-hand side makes the answer certain). Now, to be fair, this makes no difference to an if statement (e.g. 60 is still a "true" equivalent) - but even so, what's the point of it? Well, it means that you can do something similar to using boolean "bitmasks" in DSP code...
Code: Select all
x = ((a == b) && 10) || 20
# If a equals b, x now equals 10.
# If a doesn't equal b, x now equals 20.

This is a tricky technique to use, and isn't often recommended, so I'll say no more except to point out something to be careful of: even though other objects are equivalent to boolean TRUE or FALSE, they will never compare equal to the Ruby objects true or false...
Code: Select all
# Store the result of a predicate test for later...
tested = (x == y)

# Dont do this, because 'tested' may be an "equivalent object" rather than 'true'...
if tested == true
   #...
end

# Just use the predicate directly...
if tested
   #...
end

# To insist upon a true/false result, apply the "not" or "!" operator twice over.
tested = !!(x == y)
# NB: "Green" boolean RubyEdit outputs do this automatically!


Finally; what does all of this mean for the example line of code (NB: not usable code, I missed out the if's trailing lines to save space)...
Code: Select all
if @status = 144 and @note = 48 or 50 or 60

# Separate out the assignments (NB: "=" binds tighter than "or")
if (@status = 144) and (@note = 48) or 50 or 60

# Collapse the assignment expressions to their results...
if 144 and 48 or 50 or 60

# Order of evaluation...
if ((144 and 48) or 50) or 60

# Invoke operators (returning operands, not true/false).
if (48 or 50) or 60
# ...
if 48 or 60
# ...
if 48

# Boolean equivalence
if true

Especially note that all of the rules discussed above can be applied without any errors being raised - Ruby is extremely relaxed about what you can use in boolean expressions and "if" statements! Also important is that we haven't tested the values of @status and @note - we've forced them to have the answers we were looking for!
(NB: Don't generalise the first step: how the assignment's "=" binds is another area where the priority depends on how the operators are used, and the "shortcut" when an operator's left-hand-side gives a quick answer can complicate things too).

Hopefully, it's now clearer why the original code didn't work as expected. Using what we've learned here, one possible working replacement would be...
Code: Select all
if 144 == @status && (48 == @note || 50 == @note || 60 == @note)

You probably noticed that I wrote the equality tests there "backwards". When testing equality, it makes no difference logically; the reason has to do with ease of debugging...
Code: Select all
# Instead of this...
if x == 10

# Write this...
if 10 == x

# Because this is an easy mistake to make, but won't throw an error...
if x = 10

# Whereas this is always an error because you can't assign to a static value...
if 10 = x
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby newdsp » Sat Dec 19, 2020 5:15 am

It's true. My version is not working as intended plus it's also triggering the piano on note off. Once for note on and a second time for note off. Which is bad.

Turns out that your code is also wrong or it's not producing the correct result. It seems to be a bug or something because it's not supposed to pass through the ruby code but it does.
Attachments
Midi Note Filter Ruby Experiment FS306 v0.02.fsm
(457 Bytes) Downloaded 778 times
Midi Note Filter Ruby Experiment FS306.fsm
(447 Bytes) Downloaded 766 times
newdsp
 
Posts: 88
Joined: Fri Dec 11, 2020 1:57 am

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby RJHollins » Sat Dec 19, 2020 5:49 am

Thanks once again for the 'Schooling' TROG.

I've no idea the time you put into these lessons [a lot a bet] ...

Yet, you have a way to explain things in a comprehensive ... yet understandable way.

I know you've been pestered to write a Book ... but it would be amazing if it was a FS designed book that could be called up in a FS project. [search .... code examples ... copy/pasting]. That's just an idea ...

But really the FS Administration should FIX this Message Board so that youse guys spend much less time having to clean up aisles 7, 14, 87, 23, etc.

They should have a 'Lessons/Insight Tutorial' Thread that is always available, that exactly what you have posted would be easily found and accessible.

oh well .... again, just wanted to express appreciation !!
RJHollins
 
Posts: 1568
Joined: Thu Mar 08, 2012 7:58 pm

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby Spogg » Sat Dec 19, 2020 7:52 am

Very much appreciated trog. :D

That must have taken you ages to write up.

Thank you so much for this and all the other insights and interventions you make.

If I or others share any future Ruby code would you be interested in analysing and discussing it in the forum?
User avatar
Spogg
 
Posts: 3324
Joined: Thu Nov 20, 2014 4:24 pm
Location: Birmingham, England

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby tulamide » Sat Dec 19, 2020 8:19 am

Trog, you are of course completely right with everything you pointed out.

I just wanted to add a few thoughts.

When using Ruby on a regular basis, you won't fall to the "=" trap. Ruby uses "=", "==" and "===", and all have a specific meaning, which you get used to quite quickly. But yeah, as a beginner your tip is a great help.

Neither true nor false are boolean values in Ruby, not even values. They are classes, and not even immutable objects. This can lead to crazy stuff.
Code: Select all
class FalseClass
  def ==(arg)
    return true
  end
end

false == true ##this will now always result to true!


"if" is not a statement, but an object. As such, it can be assigned and used later on, but it's not dynamic.
Code: Select all
a = -1
b = if a == 1 then 0 else a * 4 end

a = -6

x = if a < b then a else b end ## will return -6, which is a, because b equals -4


the same way that multiply/divide have priority over add/subtract in maths
This is something I will always be thankful for to be German. Kids can learn this rule so much easier over here. The mnemonic is so much simpler:
Punkt vor Strich


It translates word by word to "dot before dash" and it works so well because the multiply sign in Germany is a simple vertically centered dot and the divider sign is ":" ( 6 : 3 = 2), whereas add and subtract are just the same + and -

Easy as pie!
"There lies the dog buried" (German saying translated literally)
tulamide
 
Posts: 2688
Joined: Sat Jun 21, 2014 2:48 pm
Location: Germany

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby RJHollins » Sat Dec 19, 2020 9:48 am

Thanks T.

I know only a few may respond to these types of threads. And probably the 'Coders' already know ...

But for 'dabblers' like me ... it helps.

Right now I only have a couple of FS apps/plugs that I've cobbled together. They are many Utility type of programs. There is no way I'm gonna design a better EQ or compressor than the ones I already own ... but there have been a couple things I've made that assist me in my regular work.

One of them keeps track of my Session Time, and calculates my Billable hours for Clients. This one is special, in that it only calculates the 'playing' time accumulated. It also has a few useful options. But it is this kinda plugin that nobody else has seemed to write [that works like that].

Oh there are some audio analysis or treatment ideas that float through the brain [some have touched on these here at the Forum], but mine are beyond my programming knowledge and available time to pursue.

Since I use FS mostly as a form of hobby/change of focus ... the 'teaching' you Guys post can really help ... at least lower the confusion/frustration :)

Again .... Thanks !
RJHollins
 
Posts: 1568
Joined: Thu Mar 08, 2012 7:58 pm

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby trogluddite » Sat Dec 19, 2020 3:45 pm

newdsp wrote:...not producing the correct result

A few things that I noticed looking at the schematics...

- In both of them, you have two links from the splitter to the RubyEdit; one for status and the other for data1. Whenever there is a Midi event, BOTH of these links will send a trigger to the RubyEdit, so the code will be executed twice per Midi event. Because these triggers happen one after the other, you might also get alternate states where the @status is for the current message, but the @data is still from the previous one (or maybe the other way around).

I think that doing the filtering once and then sharing the filtered data is certainly a good idea; but taking the Midi directly into the RubyEdit rather than splitting it outside of the code will avoid this duplication.

- In FS306 v0.02-1 you use variable names @note and @data inconsistently - choose one or the other!

- In FS306-1 the @data name is fine, but the precedence of && and 'or' isn't what you need. It will allow ANY message with @data == 50 to pass, because it is interpreted as...
Code: Select all
if (@status == 144 && @data == 48) or @data == 50

You can't get the precedence that you need without forcing it using parentheses in this case. i.e...
Code: Select all
if @status == 144 && (@data == 48 or @data == 50)


tulamide wrote:They are classes, and not even immutable objects.

Hey - don't be encouraging the Ruby noobs to be monkey-patching! That's evil! :twisted: :lol:

TrueClass and FalseClass are classes, yes. Strictly speaking, true and false are are instances of those classes. In fact, they're a special case of this; TrueClass and FalseClass are each allowed to instantiate only a single instance (e.g. you can't call TrueClass.new to make another one) - so true and false are "singleton" instances (also nil - and maybe a few others?).

tulamide wrote:"if" is not a statement, but an object

I really like Ruby's consistency here: just like I said about assignments before, the entire "if...end" clause is an expression which returns an object (the result of whichever branch was executed). Absolutely every code clause, even a Class definition, is an expression which returns an object of some kind (even if only nil). Unlike many other languages, "value-less" clauses such as "statements" or "procedures" don't really exist in Ruby (though you don't have to use the returned object if you don't want to, of course).

Spogg wrote:If I or others share any future Ruby code would you be interested in analysing and discussing it in the forum?

I'm always up for having a look, so long as I have the time. Having your code peer-reviewed is an important part of coding practice, even for the most experienced professionals. I also think that it's much easier to learn coding by doing it using examples that have some meaning in a familiar context, and it's more rewarding when the result does something that's useful to you. Typical tutorial code examples tend to be very contrived IMHO - we work with Midi messages and triggers, not "foos", "bars", and "bazzes"! This is especially true in FlowStone, because the way that Ruby is embedded into the graphical stuff, clever though it is, means that a lot of on-line tutorials can be inappropriate, or even severely buggy, within a RubyEdit.

And lastly, thanks to everyone for your kind comments. I haven't been very "FlowStoney" for a while, for various wonky-brained reasons. A few coffee-break brain-teasers are just the thing to ease me back into it, and they often lead to interesting tutorial points. I honestly really enjoy writing them - I just wish that, like RJ wrote, we had a better medium for sharing them.
All schematics/modules I post are free for all to use - but a credit is always polite!
Don't stagnate, mutate to create!
User avatar
trogluddite
 
Posts: 1730
Joined: Fri Oct 22, 2010 12:46 am
Location: Yorkshire, UK

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby newdsp » Sat Dec 19, 2020 6:06 pm

It looks like FS 306 is interfering with FS 309b2 and all other versions. I had to uninstall it because of that. There needs to be a way to install multiple versions without stepping on each other or interfering with each other. Here is the result. A very broken file that opens but crashes after a second or two.
Attachments
Broken.fsm
This file used to work in FS 309b2 but it's now crashing in all versions. FS 306 might have something to do with it.
(1.78 MiB) Downloaded 771 times
newdsp
 
Posts: 88
Joined: Fri Dec 11, 2020 1:57 am

Re: TRUE or FALSE?: Ruby booleans are weird!

Postby Spogg » Sun Dec 20, 2020 8:43 am

The 309b2 is horribly broken which is why we tell people to avoid it.

Ages ago I asked Malc to remove the 2 betas but he didn’t.

Your 2 versions will have shared many resources and settings in your Appdata folder, so some stuff will be from one version and some from another. The only way to run 2 versions is to rename the program and Appdata folders each time before running, so they don’t cross-pollinate. In my case I run the new FS4 alphas on a different OS partition on my other disk drive, and this avoids accidents.

I would suggest in your case you rename the program folder
C:\ProgramFiles\DSPRobotics\FlowStone
and the Appdata folder
C:\Users\[yourname]\AppData\Roaming\FlowStone
and do a fresh install of 3.06.

This means you can still open existing projects after reverting the folder names. I strongly suggest you use 3.06 for future projects and this will minimise frustrating stuff which will happen with the 3.09b2.
User avatar
Spogg
 
Posts: 3324
Joined: Thu Nov 20, 2014 4:24 pm
Location: Birmingham, England


Return to General

Who is online

Users browsing this forum: No registered users and 52 guests