How does sonic pi handle return statements from functions?

In another thread you showed me some code to transform notes given by name to notes given by midi number. I have a follow up question.

I use this function to transform the notes:

define :noteNamesToNumber do |*notes|{|n| note(n)}.ring

Then I use this function to sort them:

define :sortNotes_LowToHigh do |notes|
  intNotes = noteNamesToNumber notes
  return intNotes.sort

How does this actually work? There is no return statement in :noteNamesToNumber, still in the other function I can call

intNotes = noteNamesToNumber notes

If I write the second function as

define :sortNotes_LowToHigh do |notes|
  noteNamesToNumber notes
  return notes.sort

the result is the same. So my question is, is there an implicit return statement in :noteNamesToNumber?
Also, am I right that |*notes| passes a pointer to notes, whereas |notes| passes a copy of notes, just as in C?

Hi @solar,

You are correct that there is an implicit return.
In the Ruby language, which Sonic Pi’s language is based on, things such as functions etc always return the value that their last statement evaluates to.

Regarding *notes, the asterisk here is called the splat operator. It allows you to do several useful things, but referring back to Sam’s earlier example:

define :notes do |*ns|{|n| note(n)}.ring

puts (notes :c3, :e2).sort #=> (ring 40, 48)

What it allows us to do in this instance is call a function and pass it any number of parameters, which then get stored in the ‘splatted’ variable - ie call notes with whatever list of notes you like (and here it collects them all as an array called ns internally).
Having said that, the examples you provide above will not work as is. As a minimum, you’d need to rewrite your :sortNotes_LowToHigh function as follows:

define :sortNotes_LowToHigh do |*notes|
  intNotes = noteNamesToNumber *notes
  return intNotes.sort

(Which could of course be simplified even further (removing the return for example)

And then you could call it like this:

sortNotes_LowToHigh(:c3, :e2)

There’s plenty of further information out there on the Internet about the splat operator, but hopefully this gives you an idea. Let us know if you have any more questions!

1 Like

Thank you for your answer!

Ok thanks for this, how do I contribute to improving the documentation?
It makes no mention of the capability of functions to return values at all! Functions are only documented as grouped statements ‘doing things’.

You just use a return statement.

here is a demo function with its usage:

define :answerMe do |n|
  if n == 1
    return "hello"
  elsif n == 2
    return "goodbye" if n == 2
    return "I don't understand you"
puts answerMe 1
puts answerMe 2
puts answerMe 3

This produces output:

 ├─ "hello"
 ├─ "goodbye"
 └─ "I don't understand you"

as with many features that work in Sonic Pi, this is a ruby construct which is not documented in Sonic Pi. As such it is not guaranteed to work in future versions, but I think it is a fairly safe one to rely on. I use it a lot with more sophisticated functions.

In fact, Ruby, the language that Sonic Pi is based on, will by design automatically return the value of the last line executed in the function before it returns, without needing to explicitly say return .... So technically, this often makes using return unnecessary. Ie, as a basic example, if we have a function that we want to have return the value of a variable after it does something, this works just as well:

define :test do
  foo = [some kind of expression here]
  [... maybe some other code]

There are sometimes some considerations to make when deciding or not whether to use return, but when using functions as defined with Sonic Pi, most of the time it’s optional :slightly_smiling_face:

(And like Robin says, the use (or not) of return is a feature from Ruby, not specifically Sonic Pi - so if they decide to change the behaviour, (highly unlikely, as it is a fairly fundamental feature) then we as Sonic Pi users would also need to adapt).

1 Like