If we had to write the built-in print function using the rules we have now, it wouldn’t be very useful – just print();. We couldn’t tell it what to print. print("abc"); is an example of giving an input to a function. We can write our own functions that work the same way.
To do this, we’re going to need several new rules: what the inputs can be, how the
function sends them, how a function definition says the inputs it wants, and how it
uses them.
The basic idea is that everyone, even functions, likes to read their data from
variables. So if a function needs an integer input, we’ll give it a specially marked
integer variable. Then we’ll make up rules to get our input into that variable. There
are a lot of rules, but they’re all just for this basic idea.
To get started, here’s a 1-input function that turns integers into words. If you call showAsWord(1) this will print "one":
The new thing in the heading is the (int n) inside the parentheses. It says that showAsWord must have an integer input. It also says the input will be in a variable named n. The computer does that automatically. When you call showAsWord(4); the computer declares n and puts a 4 into it. Then it runs the function.
Now, to see how cool this is, cover up the heading. Just look from if(n<=0) on down. Those four lines are an ordinary cascading if. If we saw this anywhere else, we’d figure that n was declared somewhere and somehow got a value, but the exact way doesn’t matter.
That’s the key idea of inputs to functions. The magic part is where we declare the
variable from the heading and fill in the caller’s value. But after that, there’s no more
magic – n is just a regular variable.
Just to be sure, here’s the function being used, nothing special:
Notice how there’s no n in this part. We never need one. When we call
showAsWord(1);, the computer creates local n inside the showAsWord function, set to
1. And as we know, local variables are invisible to everyone else and never interfere
with anyone else either. n is for the function’s personal use only, which is
perfect.
Here’s a similar example, written in steps this time. I want a function that tells me whether water is "ice", "water" or "steam" at a certain temperature. The first part is easy – I’ll pretend we have variable temp and write a cascading if:
That’s not a function – just regular code – but I figured I need do know how to
actually do it before making it into a function.
Functions need names – I’ll pick printWaterState for this. I need to decide ahead of time whether the input is an int or float. Temperatures could have decimals, so float. In the code above I already picked temp for the variable name. So the heading looks like void printWaterState(float temp). The complete function is:
The same magic happens – when someone calls printWaterState(60.0f); the
60 is automatically copied into our temp variable.
To be complete, some function calls:
Notice again, there’s no temp variable here. That’s good. We run the function by
just giving it any old float.
Here’s a 1-input function to resize ourself. Whatever float we give it, we’ll be that big:
If you remember, size is really an x, y and z. This function sets them all to the one number we give it. It’s written for a program where we never want a funny x-only stretch. It’s a nice shortcut for setting x, y and z to the same value.
It feels like a real computer command. resize(0.2f); says what it does, and
does what it says. It hides that ugly localScale stuff from us. It’s doing what
functions are for.
Here’s the same idea, but with colors. A fun fact: if you set red, green and blue all to the same value, you get a “grey scale” color – black, white, or any shade of gray in-between. This shortcut color function will do that for us:
setGreyScale(0.25f); would give a grey that barely stood out from a black
background; setGreyScale(0.9f); would turn us almost white.
Like any other functions, functions with inputs can also do math. Suppose we have a lot of artist-programmers who prefer the standard 0-255 ints for color values. We can write a function that turns us red, using 0-255 for how red we should be:
Now setRed(80); turns us a darkish red, and setRed(240); is almost maxed-out
red (if you’ve used image programs, those numbers would seem completely
natural.)
Here’s a longer example with a string input. I want the function to move into a corner of the screen, using "UL", for upper-right, and "LL", "UR", and "LR" for the other corners. Here’s the heading:
My plan is to figure out the left&right and top&bottom numbers. I’ll only need 2 of them, but it’s easier to compute them all. Then I’ll check the input and move us to whichever corner. This has got local variables, calculations and so on. By now we know functions can have all that:
For fun, I decided that bad inputs, like "topEast", put us in the middle and print an error message. That’s not great, but what else can you do?
To finish it, some function calls:
The second call is why we don’t like to use strings as instructions – too easy to
spell them wrong.
Now some bonus function calls to show a new rule:
We’re allowed to use math inside of a function call – +, variable look-ups, any math we can think of. In fact, we’ve been doing it all along. print("n is "+n); computes "n is 6" and then calls print.
Most of these are just formal ways of saying stuff you already know, but some of it might be new. Plus it’s just nice to see it written up:
These words only matter because the computer uses them, a lot, so they’re good to know. A bad argument means the call used a wrong input. If a local shadows a parameter, that’s inside the function. They don’t really relate to normal english usage. They often get shortened to arg and parm (parm isn’t even the first four letters of parameter, but oh well.)
Another way of seeing this, you can only run the function if you give it usable inputs.
Again, just a summation of the rules from the examples, but I tried to use the official words. Plenty of rules are like this – pretty intimidating until you understand them.
The name of the parameter really is just any name you pick. My original showAsWord used n as the parameter name, but anything else would work. This looks worse, but works fine – all that matters is the code is reading the input variable:
The style rules for parameter names are the same as for any variables: try to pick
one that says what it does, or just something boring like n, w, or x if you can’t think
of a good name.
Once the function is running, the parameters count as regular variables. You’re allowed to change or copy them if you like. In this example, I decided to “fix” the input before using it:
If I call showAsWord(-4);, it starts with n=-4;. But then the first line changes n
to 0, which is legal, and seems fine.
After they get the inputs, those variables don’t have any special “input goodness.” In this example, I picked a long variable name for the heading – hoping it makes it easier for people to see what the function does. But I want to use a shorter name inside, so I copy it to x:
You’re even allowed to ignore or destroy the inputs if you want. In this example, I want to test how the function works on 29. This temporarily ruins the function, but it’s legal, and is fine for testing:
This one simply ignores the input, so is useless, but it’s also legal. And also the kind of thing people write for testing:
Sometimes we call that a Stub. The idea is: if you want to finish a function later,
as least make the heading correct. That way everyone can write their parts using
it.
The parameter name really is a local variable, only inside that function. In this example, Start can also have an n:
Start gets to have an n with no surprises. As usual, the n inside the function is completely different. It’s just an unimportant coincidence the names happen to be the same.
I mentioned some of these before, but here are almost all the function errors and error messages. Same idea as before – if we cause them on purpose and read them, they won’t surprise us when we see them for real:
This gives you: A local variable named ’n’ cannot be declared in this scope because it would give a different meaning to ’n’, which is already used in a ’parent or current’ scope to denote something else. You can’t have two local variables both named n.
The error looks so odd because parameters aren’t exactly local variables.
Functions can have 2 or more inputs. There are only a few extra rules for this, which aren’t complicated. Here’s an example of a 3-input mad-lib function and some sample calls:
The two new rules are: put commas between them (both places: the function call,
and the definition.) Rule two: when the function is called, match args to parms
exactly in order.
If you remember, that clunky applyColor(); used global variables. We can finally make the good version, where we directly give it the color values:
To make orange we can use colorChange(1, 0.5f, 0);. This is a good solid use
of the “make new commands” idea of functions – one short all-in-one command to
fully set your color.
Here’s one for resizing that does a little extra thinking for us. A basic resize ends with =new Vector3(wide, tall, 1);. The last slot, z is almost always 1 (it’s deepness, which we can’t really see,) but we still have to enter it.
We can write a function that “knows” our rules, filling in that 1 automatically:
We’d use it like resize(2, 0.5f); to make us wide and short. We don’t have to
type the longer localScale line and we don’t have to think about the thickness. That’s
a good deal.
Just to show float and string inputs together, here’s an odd resize. It’s a little fakey: I want to send the overall size, then a letter whether it’s a square ("S",) a tall rectangle ("T") or a wide rectangle ("W"):
Like I wrote, this is a bit silly – I just wanted to show we could use two different types of inputs.
Arguments really are always matched to parameters in the exact order. That means we can now have some new, fun input out-of-order errors. sayStory takes inputs in order (string, string, int). Putting the int somewhere else is an error:
The computer tries to match 5 with string animal. It can’t, so it’s an error.
In our minds, we accidentally put 5 in front, instead of at the end. But the
computer won’t even think about different orders. As soon as it sees that the 5
doesn’t match, it stops checking and gives two errors: The best overloaded method
…has some invalid arguments and then more detail: Argument #1 cannot convert int
expression to type string.
You have to give the exact number of inputs. Too many or too few is an error. Both of these give a No overload for method takes __ arguments error:
You might think the second one should just skip the extra 7. We decided it’s
an error so you have a chance to clear it up. Maybe you meant to write
5+7?
Of course, the computer only checks the types. It has no way to make sure the first input is an animal. If you flip the order of two strings, it won’t cause an error, but will probably be wrong:
You aren’t allowed to use the multiple variable shortcut for parameters. You have to list out each type/name pair. This is an error:
The computer can’t tell whether y is supposed to be a float, or if you forgot to put the type in front. My compiler tells me identifier expected, which isn’t very helpful, but at least it tells me the line.
The correct version is void setPos(float x, float y).