Sunday, May 23, 2010

Functions

After reading the last chapter on SynthDefs and the one before that on Variables, Nicole feels like she's got the hang of SuperCollider so she's dropped out of grad school to work for the new SuperCollider start-up company SuperSounds.com. On her first day, her boss tells her to write a function that prints "hello world" four times. "No problem," she thinks and goes to look at previous posts. Functions are code blocks encased by curly brackets, { } and hello world is easy enough. So she writes:

(
{

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
}
)

Then she thinks, "I should have asked for a signing bonus." She tries running it, by double clicking to the right of the top parenthesis and hitting enter. In the Untitled output window it says, "a Function"

What's the problem? She declared a variable called greet. greet gets "hello world". Then she sends a postln message to greet four times. Every line ends with a semicolon . . .

Then she realizes that she defined a function, but never told the interpreter to run it. The interpreter saw a code block surrounded by curly brackets and thought "a function!" Then it thought, "What do you want me to do with this? Nothing? Ok, I'll throw it away." So Nicole modifies her code:

(
var func;

func = {

var greet;
greet = "hello world";

greet.postln;
greet.postln;
greet.postln;
greet.postln;
};

func.value;
)

And then it works great. value is a message you can send to functions. It means, "Run yourself!"

But then Nicole gets a message from her boss saying, "Sometimes, we need to print out hello world five times and once in a while, three times, and rarely, it needs to print out infinite times." Nicole considers writing a few different versions of the function and calling them func4, func3, etc, but then remembers about arguments to functions.

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({
greet.postln;
});
};

func.value(4);
)

When she writes her function, she declares to the interpreter that the function takes one argument. An argument is a special type of variable that gets set when the function is called. When she calls the function withfunc.value(4);, she's assigning a value to the argument repeats.

Then, inside the function, she's written, repeats.do. What is 'do'? It's a message. It takes a function as an argument. 'do' is a message you can send to integers that runs the function passed as an argument the number of times as the integer that it was called on. In this example, repeats is 4, so it runs four times.

What is an integer? An integer is a whole number. -2, -1, 0, 1, 2, 3, 4, etc. There is a special integer in SuperCollider called inf. It means infinity. If we try calling our above function, with

func.value(inf);

hello world will print out forever, or until we stop the program by hitting apple-. .

Then Nicole's boss sends another email saying that marketing has some changes. Every line needs to start out with the line number, starting with zero. So she makes another change:

(
var func;
func = { arg repeats;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value(4);
)

The function called by do takes an argument. And that argument is the number of times the loop has been run, starting with 0. So her output from this program is:

0 hello world
1 hello world
2 hello world
3 hello world

post just means print without a new line at the end.

Almost every time, she runs this function, the argument is going to be 4. So she can declare a default argument for the function.

(
var func;
func = { arg repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

When she calls func.value, if there's no argument, then the interpreter will assign 4 to repeats by default. If she calls it with, func.value(6); then repeats gets 6 instead. What if she accidentally passes in something besides an Integer into her function? That depends. If the object she passes in also understands a do message, then it will use that instead, although the result may differ. Otherwise, she will get an error.

What if the function took a lot of arguments with default values?

(
var func;
func = { arg foo = 0, bar = 0, baz = 1, repeats = 4;

var greet;
greet = "hello world";

repeats.do ({ arg index;
index.post;
" ".post;
greet.postln;
});
};

func.value;
)

If she wants to pass in arguments, she does it in the order they're declared in.

func.value(0 /* foo */, 0 /* bar */, 1 /* baz */, 3 /* repeats */);

However, if we're happy with all the default values, there's a way to tell the function just to assign to a particular variable, out of order:

func.value(repeats: 3);

You can also just pass in the first N arguments and leave the remainder to the default values:

func.value(3, 1);

And you can combine approaches:

func.value(2, repeats: 6);

Those slash-stars up there are comments. The interpreter ignores everything between a forward slash star and a backslash star. They can span multiple lines. You can also create single line comment by using forward slash forward slash:

// This is a comment

Some times it's useful to comment out a line of code while debugging. So you can skip a particular line in order to figure out where your error is.

The philosophical point of a function is to return a value. Doing things within functions, like printing are technically called side effects. What the function returns is the value of its last line. Let's change that function so it returns the number of times that it printed.

(
 var func;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 func.value;
)

Now, if we create a new variable called times, we can assign the output of the function to it.

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 
 times = func.value;
 times.postln;
)

Prints out hello world with the line number like before, and then at the bottom, prints out a 4. Or we could change those last two lines from

 times = func.value;
 times.postln;

to

func.value.postln;

and the interpreter will read that statement left to right, first finding the value of the function and then sending that value a postln message.

Ok, what happens if we take the above function and write some code below it that looks like this:

(
 var func, times;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
 greet.postln;
)

We get errors.

• ERROR: Variable 'greet' not defined.
 in file 'selected text'
 line 14 char 6 :
 greet•.postln;

This is because of something called scope. Variables only exist in the code block in which they are declared. Code blocks are zero or more lines of code surrounded by curly braces or highlighted by the user. This means that variables and arguments declared inside a function only exist inside that function. index does not exist outside of its function, which is the function passed as an argument to repeats.do. greet does not exist outside of it's function. None of these variables exist of the text we highlight.

Variables in outer blocks are accessible within inner blocks. For example,

(
 var func, times;
 times = 0;
 func = { arg repeats = 4;
 
  var greet;
  greet = "hello world";
  times.postln;
  
  repeats.do ({ arg index;
   index.post;
   " ".post;
   greet.postln;
  });
  repeats;
 };
)

Is fine because times exists in the outer most block. In the same way, we can use greet inside our repeats.do function.

There are, however, a few variables that can be used everywhere. The interpreter gives us 26 global variables. Their scope is all of SuperCollider. They are single letter variable names, a, b, c, d, e, f, etc. You don't need to declare them and they keep their value until you change it again, even in different code blocks. This is why the variable 's' refers to the Server. You can change that, but you might not want to.

If you have any questions about functions, you can look at the Function help file for more information. The most useful message you can send to a function is value, but there are a few others, which you can read about. You may not understand everything you see in the helpfiles, but it’s good to keep reading them.

Summary

  • Blocks of code surrounded by curly brackets are functions.
  • Functions do not run unless you tell the interpreter to run them, which you can do with the message "value".
  • You can declare arguments to function. That argument can have a default value.
  • An integer is a whole number
  • If you pass the message "do" to an integer with a function as an argument, that function will run as a loop.
  • Functions can have many arguments. You must pass arguments in the correct order or list them by name.
  • Comments are either surrounded by /* and */ or are on a single line, preceded by //
  • All functions return a value, which is the result of the last line of code in the function
  • Variable names are only valid in the block of code in which they were declared, and within blocks of code contained within that block.
  • There are 26 global variables, each a single lowercase letter a-z.

Problems

  1. Write a function with nested do-loops, so that there is one loop inside the other.
  2. If x and y are intergers and the inner loop is x.do and the outer loop is y.do, how many total times with the inner loop be run?

1 comment:

  1. I should add that your code will - in the earl examples print:

    hello world
    hello world
    hello world
    hello world
    4

    Or,

    0 hello world
    1 hello world
    2 hello world
    3 hello world
    4

    I wonder whether her boss might ask why the extra 4, or why the 0 - 3! Of course if you program you understand that computers count from zero, however, your boss and his clients may want an explanation of how to right this little problem?

    Thanks, I'm enjoying your tutorials.

    ReplyDelete