The Sway Reference Manual/Functions
Recall, the series of expressions we evaluated to find the y-value of a point on the line
y = 5x - 3
given an x-value:
sway> var m = 5; INTEGER: 5 sway> var x = 9; INTEGER: 9 sway> var b = -3; INTEGER: -3 sway> var y = m * x + b; INTEGER: 42 sway> y; INTEGER: 42
Now, suppose we wished to find the y-value corresponding to a different x-value or, worse yet, for a different x-value on a different line. All the work we did would have to be repeated. A function is a way to encapsulate all these operations so we can repeat them with a minimum of effort.
Encapsulating a series of operations
editFirst, we will define a not too useful function that calculates y give a slope of 5, a y-intercept of -3, and an x-value of 9 (exactly as above). We do this by wrapping a function around the sequence of operations above. The return value of a function is the value of the last thing evaluated.
function y() { var m = 5; var x = 9; var b = -3; m * x + b; //this quantity is returned }
There are a few things to note. The keyword function indicates that a function definition is occurring. The name of this particular function is y. The stuff between the curly braces is the code that will be evaluated (or executed) when the function is called. This code is not evaluated until then.
You can copy and paste this function into the Sway interpreter. If you do, you'll see something like:
sway> function y() more> { more> var m = 5; more> var x = 9; more> var b = -3; more> m * x + b; //this quantity is returned more> } FUNCTION: <function y()>
Notice that the interpreter prompt changes to more> when the input is incomplete. In the case of the function definition above, that occurs when the curly close brace is entered[1].
Once the function is defined, we can find the value of y repeatedly:
sway> y(); INTEGER: 42 sway> y(); INTEGER: 42
The parentheses after the y indicate that we wish to call the y function and get its value.
The y function, as written, is not too useful in that we cannot use it to compute similar things, such as the y-value for a different value of x. But before we improve our function, let's modify it so that it displays the current environment[2]. This may help you to understand what happens in a function call. while the body of the function is executing:
function y() { var m = 5; var x = 9; var b = -3;
pp(this);
m * x + b; //this quantity is returned }
When we call the new version of y, we see its current environment, which has bindings for b, x, and m.
sway> y(); <OBJECT 2566>: context: <OBJECT 749> dynamicContext: <OBJECT 749> callDepth: 1 constructor: <function y()> this: <OBJECT 2566> b: -3 x: 9 m: 5 INTEGER: 42
The variables b, x, and m are known as local variables since they are not directly visible outside the neighborhood of the function body.
Passing arguments
editA hallmark of a good function is that it lets you compute more than one thing. We can modify our function to take in the value of x in which we are interested. In this way, we can compute more than one value of y. We do this by passing in an argument, in this case, the value of x.
function y(x) { var slope = 5; var intercept = -3; return slope * x + intercept; }
We give names to the values being passed in by placing variable names between the function definition parentheses. In this case, we chose x as the name. Notice that since we are passing in x, we no longer need (or want) the definition of x, so we delete it. Now we can compute y for an infinite number of x's:
sway> y(9); INTEGER: 42 sway> y(0); INTEGER: -3 sway> y(-2); INTEGER: -13
What if we wish to compute a y-value for a given x for a different line? One approach would be to pass in the slope and intercept as well as x:
function y(x,slope,intercept) { return slope * x + intercept; } sway> y(9,5,-3); INTEGER: 42 sway> y(0,5,-3); INTEGER: -3
If we wish to calculate using a different line, we just pass in the new slope and intercept along with our value of x. This certainly works as intended, but is not the best way. One problem is that we keep on having to type in the slope and intercept even if we are computing y-values on the same line. Anytime you find yourself doing the same tedious thing over and over, be assured that someone has thought of a way to avoid that particular tedium. So assuming that is true, how do we customize our function so that we only have to enter the slope and intercept once per particular line? We will explore three different ways for doing this. In reading further, it is not important if you understand all that is going on. What is important is that you know other approaches exist and understand the pros and cons of each approach:
Creating functions on the fly
editSince creating functions is hard work (lots of typing) and Computer Scientists avoid hard work like the plague, somebody early on got the idea of writing a function that itself creates functions! Brilliant! We can do this for our line problem. We will tell our creative function to create a y function for a particular slope and intercept! While we are at it, let's change the variable names m and b to slope and intercept, respectively:
function makeLine(slope,intercept) { function y(x) { slope * x + intercept; } y; }
The makeLine function creates a local y function and then returns it. This next version is equivalent:
function makeLine(slope,intercept) { function y(x) { slope * x + intercept; } }
Since the last thing makeLine does is to define the y function, the y function is returned by a call to makeLine.
So our creative function simply defines a y function and then returns it. Now we can create a bunch of different lines:
sway> var a = makeLine(5,-3); FUNCTION: <function y(x)> sway> var b = makeLine(6,2); FUNCTION: <function y(x)> sway> a(9); INTEGER: 42 sway> b(9); INTEGER: 56
Notice how lines a and b remember the slope and intercept supplied when they were created[3]. While this is decidedly cool, the problem is many languages (C and Java included) do not allow you to define functions that create other functions. Fortunately, Sway does allow this.
Using objects
editAnother approach to our line problem is to use something called an object. In Sway, an object is simply an environment and we have seen those before. So there is nothing new here except in how to use objects to achieve our goal. Here, we define a function that creates and returns a line object. A function that creates and returns an object is known as a constructor.
function line(slope,intercept) { this; }
The this variable always points to the current environment, which in this case includes the bindings of the formal parameters slope and intercept. By returning this, we return the environment of line, and we can look up the values of slope and intercept at our leisure. To prove that slope and intercept exist, we can use the built-in pretty printing function, pp:
sway> m = line(5,-3); OBJECT: <OBJECT 231> sway> pp(m); <OBJECT 231>: context : <object 145> dynamicContext: <object 145> constructor: <function line(slope,intercept)> this: <object 231> intercept: -3 slope : 5 OBJECT: <OBJECT 231>
We access the variables in an object with the '.' (dot) operator:
sway> m . slope; INTEGER: -3 sway> m . constructor; FUNCTION: <function line(slope,intercept)>
Now we modify our y function to take in a line object as well as x and use the dot operator to extract the line's slope and intercept:
function y(line,x) { line . slope * x + line . intercept; }
In this scenario, we create different lines, then pass each line to our new y function:
sway> var m = line(5,-3); OBJECT: <object 231> sway> var n = line(6,2); OBJECT: <object 256> sway> y(m,9); INTEGER: 42 sway> y(n,9); INTEGER: 56
The problem with this approach is we have separated line objects from finding y values, yet these two concepts are closely related. As an example, suppose we have parabola objects as well as line objects. Our y function would fail miserably for parabola objects even though the concept of (x,y) points on a parabola is just as valid as points on a line[4].
In the object-oriented world, we solve this problem by bundling the object and functions that work specifically on that object together. In our case, we make the y function part of the line object:
function line(slope,intercept) { function y(x) { slope * x + intercept; } this; }
This is very similar to the functions-on-the-fly approach, but we return this instead of the function bound to y. Now we call the y function via the line object.
sway> var m = line(5,-3); OBJECT: <object 231> sway> var n = line(6,2); OBJECT: <object 256> sway> m . y(9); INTEGER: 42 sway> n . y(9); INTEGER: 56
Should we have a parabola object, it would have its own y function with a different implementation. We would call it just the same, however:
sway> var p = parabola(2,0,0); OBJECT: <object 453>
sway> p . y(7); INTEGER: 49
This approach is supported in object oriented languages such as Java. The earlier approach (where the function was separated from the object) is supported in procedural languages such as C.
Functions versus operator
editAll operators are functions and can be called using operator syntax. For example, the following expressions both sum the values of a and b:
var sum = a + b; var sum = +(a,b);
Conversely, any function of two arguments can be called using operator syntax. Sometimes using operator syntax makes your code more clear. Let's make a function that increments a variable by a given amount, similar to the C, C++, and Java operator of the same name:
function +=($v,amount) { $v = force($v) + amount; }
Don't worry about how the code works; just note that the += function has two formal parameters ($v and amount) and thus takes two arguments. We can call += to increment a variable using function call syntax:
var x = 2; +=(x,1); inspect(x);
or we can use operator syntax:
var x = 2; x += 1; inspect(x);
In both cases, the output of the code fragments is the same:
x is 3
Functions that are called using operator syntax have the same precedence level as the mathematical operators and are left associative.
Footnotes
edit- ↑ Typing a long construct into the interpreter is tedious. In a later chapter, we will learn how to store our code in a file and have the interpreter execute the code in that file. If we need to make changes to our code, we simply edit the file. That way, we do not need to type in the modified code into the interpreter from scratch.
- ↑ Sway lets you redefine variables and functions.
- ↑ The local function y has, as its context, the local environment of the makeLine function. This environment holds the bindings for slope and intercept.
- ↑ Here is a concrete example of trying to generalize, so that our function works for all objects for which the concept of the function is valid.