When writing an essay, we break our ideas up into paragraphs each focusing on one idea. This makes the essay easy to read and understand. Programmers do the same thing with their code using subroutines. An essay consisting of one huge paragraph is not the most elegant paper. One large chunk of code is not the most elegant script. In this article, I will demonstrate how to organize your code using subroutines.
The Basic Subroutine
on subroutine_name()
--do action here
return some_value
end subroutine_name
You can name your subroutine whatever you want (normally something that makes sense). You enclose your subroutine like a regular statement with on and end commands. You can have your subroutine do any action that a regular script could do. The only difference is, the subroutine must return a value. You can either return a value using the return command, or you can omit the return command and the subroutine will return the last value by default. I will talk about the parentheses later.
To use a subroutine, you use the first line of the subroutine but omit the word 'on'. Essentially, you call a subroutine by typing its name followed by the parentheses. For example, this subroutine would be called with this code:
subroutine_name()
Here is an example:
SimpleSubroutine()
--use the subroutine (programmers say call subroutine)
on SimpleSubroutine()
return "hello"
--Run this script and look at the result window
end SimpleSubroutine
--The subroutine called. It can be anywhere in the script.
As I mentioned before, the subroutine returns a value. Therefore, we can treat our subroutine like any other type of data. If the value is a string, we can put in a dialog box.
on SimpleSubroutine()
return "hello"
end SimpleSubroutine
display dialog SimpleSubroutine()
--This line is equivalent to display dialog "hello"
We can set it to a variable.
on SimpleSubroutine()
return 5
end SimpleSubroutine
set x to SimpleSubroutine()
x > 10
--> false (Run this script and look at the result window)
Of course, most of the time, subroutines are more complex than just a few lines of code, but these simple subroutines help demonstrate what subroutines can do.
Those Empty Parentheses
The empty parentheses are very useful. Try this script:
set x to 5
SimpleSubroutine()
on SimpleSubroutine()
x > 10
end SimpleSubroutine
You got the error message; "The variable x is not defined," didn't you? That's because variables loose their values when they enter a subroutine. Think of a subroutine as a new script almost. Variables need to be redefined in new scripts. However, we know of a special variable called a property that doesn't need to be redefined each time a script is run. Properties can also be used in subroutines. Try this script:
property x : 5
SimpleSubroutine()
on SimpleSubroutine()
x > 10
--false
end SimpleSubroutine
However, remember that properties are supposed to be used sparingly (like chili powder). We can use regular variables in subroutines, by placing them in the parentheses. Try this script:
set x to 5
SimpleSubroutine(x)
on SimpleSubroutine(x)
x > 10
--false
end SimpleSubroutine
TIP: Variables don't even have to have the same name between subroutines and the regular script. If I wanted, I could set x to 5 outside my subroutine and then decide to use the variable num inside my subroutine. For example:
set x to 5
SimpleSubroutine(x)
on SimpleSubroutine(num)
--it's the same variable just with a new name!
num > 10
--false
end SimpleSubroutine
Recursive Subroutines (Or: Why Subroutines Are Useful)
Granted, you could just divide your script up by placing comments between each of your script sections, but subroutines are used all the time, so it helps if you can recognize them and be able to use them. Also, the recursive subroutines are extremely useful.
A recursive subroutine is a subroutine that needs to be called multiple times in one script. Say you had a subroutine of 10 lines that you called 5 times. If you didn't use the subroutine, you would have to type out 50 lines of code instead of 10.
A variation on the recursive subroutine is the infinite subroutine. Sometime in your scripting career, you will need an action to call upon itself. The only possible way of doing this is using a subroutine. For example, say you had a dialog box with two options. Option 1 displayed a new dialog box. Option 2 displayed the exact same dialog box. Sounds easy right? It's not because, you will be writing code forever. Here is an example that allows option 2 to be selected three times. On the fourth time, the script just stops.
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
display dialog "This is a new dialog box." buttons ["OK"] default button 1
else
--create the original dialog
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
display dialog "This is a new dialog box." buttons ["OK"] default button 1
else
--create the original dialog
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
display dialog "This is a new dialog box." buttons ["OK"] default button 1
else
--create the original dialog
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
display dialog "This is a new dialog box." buttons ["OK"] default button 1
--too much code!
end if
end if
end if
end if
Each time option 2 is selected; you have to write the exact same script that created the first dialog box. At some point, you will have to stop. However, we can use a subroutine. Subroutines are great ways to prevent code from being repeated.
Here's the original dialog box:
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
The user can make a choice, so we need an if-then statement.
if button returned of result = "Button 1" then
--if the user selects button 1
display dialog "This is a new dialog box." buttons ["OK"] default button 1
-- then display a new dialog box
end if
Now let's enclose our script in a subroutine.
on DialogBoxes()
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
--if the user selects button 1
display dialog "This is a new dialog box." buttons ["OK"] default button 1
-- then display a new dialog box
end if
--so far, nothing happens if button 2 is pressed
end DialogBoxes
To run the subroutine, we must call it:
DialogBoxes()
If button 2 is pressed, we want the dialog box to be displayed again, so we need to call the subroutine that we just created. Luckily, subroutines can be called from within themselves. So we need to add
else
DialogBoxes()
to our if-then statement. Now our script is complete:
DialogBoxes()
on DialogBoxes()
display dialog "Click Button 1 for a different dialog box. Click Button 2 for the same dialog box." buttons ["Button 1", "Button 2"] default button 1
if button returned of result = "Button 1" then
--if the user selects button 1
display dialog "This is a new dialog box." buttons ["OK"] default button 1
-- then display a new dialog box
else
DialogBoxes()
--if the user selects button 2, run this subroutine again
end if
end DialogBoxes
Would you ever need to call the same dialog box from itself? Probably not. Are there times where you will have to call a subroutine from within itself? Definitely.
Silly Subroutine Tricks
Let's say we have two subroutines:
on choice1()
return "choice one"
--Look at the result window
end choice1
on choice2()
return "choice two"
--Look at the result window
end choice2
What if we only wanted to call one of these subroutines, but the one we wanted to call wasn't always the same one? We can set subroutines to other subroutines.
set theList to {choice1, choice2}
--make a list of all your choices
set RunHand to item 1 of theList
--set a variable to one of the choices (in the case, choice1)
RunHand()
--call the handler. This is equivalent to typing choice1().
To have the user select which subroutine they want to call, make a list.
set someList to {1, 2, 3, 4, 5}
--make a list with numbers
set theChoice to choose from list someList
--thechoice = the number the user selected
set theList to {choice1, choice2, choice3, choice4, choice5}
--a list of all your choices
set RunHand to item theChoice of theList
--selects the choice with the same number that the user selected
RunHand()
--call the handler.
on choice1()
return "choice one"
--Look at the result window
end choice1
on choice2()
return "choice two"
--Look at the result window
end choice2
on choice3()
return "choice three"
--Look at the result window
end choice3
on choice4()
return "choice four"
--Look at the result window
end choice4
on choice5()
return "choice five"
--Look at the result window
end choice5
Run this script. The number you selected runs a specific subroutine and displays text in the result window. Why is this useful? It replaces a long string of if-then statements. If we didn't use subroutines, the script would look like this:
set someList to {1, 2, 3, 4, 5}
--make a list with numbers
set theChoice to choose from list someList
if theChoice = {1} then return "choice one"
if theChoice = {2} then return "choice two"
if theChoice = {3} then return "choice three"
if theChoice = {4} then return "choice four"
if theChoice = {5} then return "choice five"
Now there's nothing wrong with this really; both methods take about the same amount of time to complete, and it's really a matter of preference. I personally am not a fan of the nested if-then statements. I would rather use subroutines; however, if you are more comfortable using nested if-then statements, go ahead. I am just trying to broaden your horizon.
The Last Word
Now you can break your scripts into little pieces to organize your scripts, to cut down on code, and to make them more readable. There are many different types of subroutines and different ways of calling and identifying them. For now, the simplest way is the best way.
Learning how to create subroutines completes the AppleScript Basic Training course. In these articles we learned how AppleScript works (by sending messages called AppleEvents between applications on your computer), how to make decisions using the if-then statements, how to create an interface for your programs using dialog boxes, how to store data in variables, how to repeat monotonous tasks using repeat loops, and how to organize scripts using subroutines. Hopefully, you will now be able to create your own basic scripts using these techniques.
AppleScript has much more to offer, however. In upcoming articles, I'll show you how to master AppleScript using new techniques, how to extend AppleScript's functionality, and show you where AppleScript really shines performing some really cool tasks.
More Info
Join the AppleScript gang at the Coder's Corner in The Mac Observer Forums! We discuss AppleScript related news and updates, extract really cool and useful scripts, ask questions, and give answers. It's a lot of fun and a great resource for all your scripting needs! Also, if you have a perplexing AppleScript problem, please send it to [email protected] and I'll be more than willing to help you out!