As mentioned in a previous lesson, functions are little information processors included in LiveCode. You pass the function some information, and the function returns a value to you based on that input. Some functions, like
the date and
the time, return a value reflecting the current state of the computer’s operating system. The value a function returns changes according to circumstances; that is, when you call a LiveCode function, the value it returns is based on the information you feed it.
You may recall that the LiveCode scripting language provides many built-in functions. They can appear in two forms: the “algebraic” form, in which the arguments are enclosed in parentheses and the “prose” form with no parentheses. So function calls in this format:
put random(15) into myRandom put sqrt(16) into mySqRt
are functionally equivalent to functions calls in this format:
put the random of 15 into myRandom put the sqrt of 16 into mySqRt
Just as you can create custom message handlers—,sometimes also called command handlers—you can also write your own, custom-made functions to do specialized calculations and data manipulation. A custom message/command handler looks like this:
on myOwnHandler beep put "I did it myself!" into fld "output" # do other things end myOwnHandler
Writing a function handler is very similar to writing a message handler, except that it begins with the keyword
function instead of
on. It must also include a
return statement as the last statement in the handler, which sends the result of the function back to the calling statement. Here is an example of a custom function. This one takes the full file path to a file as an input and returns the path to the enclosing folder.
function enclosingFolder pFilePath set the itemDelimiter to "/" delete last item of pFilePath return pFilePath end enclosingFolder
Custom functions work just like built-in ones, except that you can only use the “algebraic” form (using parentheses), not the “prose” form, which is allowed only for built-in functions. Here is how you could use this custom function. If I wanted to get the parent folder for a specific file on my hard drive, I would first create the custom function above,
enclosingFolder, in my stack script, then call it from any handler in that stack, like this:
answer file "Choose a file:" put it into tFilePath put enclosingFolder(tFilePath) into tFolderPath
It is theoretically possible to do everything in message/command handlers, but there are times when writing a custom function is more useful or more efficient. How do you decide when to use which?
Here are some rules of thumb:
When to write a message/command handler:
For example, if you are adjusting the layout of controls on a card via a script, you would most likely do this in a message/command handler.
When to write a function handler:
For example, let's say that in your program you need to calculate the distance between two points on a card. This is a problem that can easily be solved by writing a function. The function would be general and could easily used in any stack, regardless of what specific control objects the stack contained.
Let's take a closer look at how to write a function handler. Here is the basic syntax:
function functionName parameter list statement list return return value end functionName
functionName is the name of the function (using the same rules as message handler names; i.e., begins with letter or underscore (_), can contain any combination of letters, numbers and underscore, but no punctuation or special characters (*,&,%,-, etc.))
parameter list is an optional, comma-separated list of variable names that hold the data being passed to the function
statement list is one or more LiveCode statements that calculate or derive the value you want to produce in the function
return value is the calculated or derived value that you want to return to the statement that calls the function.
Note that the
return statement is normally the last statement in the function handler, since
return hands script execution back to the calling handler. Nothing after the
return line will be executed.
Let’s look at a practical problem that we could solve with a well-written function. Imagine a simple fuel efficiency calculator that calculates the number of miles per gallon of fuel that a vehicle acheives. First of all what pieces of information would we need to make that calculation? Simple, right?—the number of miles traveled and the number of gallons of fuel consumed. You could write the function like this:
function mpg pTotalMiles, pGallonsFuel put pTotalMiles / pGallonsFuel into tMPG return tMPG end mpg
Because the function is general in nature you would likely place it someplace fairly high up in the message-passing hierarchy, such as the stack script.
Once the function was created and saved in the stack script, you could call it from anywhere in the stack. For instance, you might have a button and three fields on a card. One field would hold the number of miles traveled, while the other would hold the number of gallons of fuel used. The button, of course would call the function and report the result in the third field. The buttons script might look something like this:
on mouseUp put mpg(fld "miles", fld "gallons") into fld "report" end mouseUp
There are ways to improve this function. You could apply a
round() function to the calculation to make the answer a little shorter. You could generalize that name of the function so it wasn't focused only on North American units of measure (although the calculation itself wouldn't change if you were using kilometers and liters instead of miles and gallons.) But this is a very simple example designed to show the format and usage of custom functions. Read on for a more complex example, for which writing a custom function might be more worthwhile.
The previous example was straightforward but somewhat simplistic. Let’s consider a more complicated problem. Say you have a need in your stack to calculate the distance between two points on your card. The calculation is based on the Pythagorean theorem for calculating the length of the hypotenuse of a right triangle, a2 + b2 = c2. It’s not hard, but the calculation is long enough that you wouldn’t want to type it out each time you need to do it. This is a prime candidate for a custom function.
To make this calculation you would need to send two x,y coordinates to the function. Here is what it might look like:
# The function call, in a button script: on mouseUp put the location of graphic "dot1" into tPoint1 put the location of graphic "dot2" into tPoint2 put distance(tPoint1,tPoint2) into tDist end mouseUp # The function handler, probably in stack script function distance pPoint1, pPoint2 put item 2 of pPoint1 - item 2 of pPoint2 into dy put item 1 of pPoint1 - item 1 of pPoint2 into dx put sqrt((dy^2) + (dx^2)) into tDistance return tDistance end distance
Let’s look at another practical problem that we could solve with a well-written function. In an older DigHT assignment that is no longer used Web-based Content Assignment students were to devise a way to resize the graphics being displayed so that they would fit into the available space on the card. This means that for each referenced image you display, you need to determine how much it would need to be resized in order to fit. The process is similar for each image and can be solved using a generalized algorithm (a set of well-defined steps)—in other words, this is a perfect candidate for a function.
The algorithm for proportionally resizing an image—or indeed any rectangular object—is straightforward:
the formattedWidthin the LiveCode dictionary.)
newHeight = origHeight X resizeFactor
newWidth = origWidth X resizeFactor
In LiveCode the code would look like this:
put the formattedWidth of image "mypic.jpg" into nativeWidth put the formattedHeight of image "mypic.jpg" into nativeHeight put 2 into resizeFactor -- assuming you want to enlarge the image by 2X set the width of image "mypic.jpg" to nativeWidth * resizeFactor set the height of image "mypic.jpg" to nativeHeight * resizeFactor
We’re part of the way there. In the example above we arbitrarily chose a resize factor—a bad practice if you want to write flexible code. What we need instead is a way to figure out the ideal dimensions, height and width, that we want to constrain the image to. One way to do this is to create a rectangle graphic and size it to exactly the maximum height and width that no image should ever exceed. Then we can calculate how much the image would have to grow or shrink to fit within that area.
In the illustration at right the image is both too wide and too tall to fit within the maximum dimensions outlined by the graphic "maxDim". We’ll work first with the width. By dividing the width of the graphic by the width of the image we can come up with the resize factor. Then we simply use that factor in our algorithm:
put the formattedWidth of image "windmills.jpg" into nativeWidth put the formattedHeight of image "windmills.jpg" into nativeHeight put the width of grc "maxDim" / nativeWidth into resizeFactor set the width of image "windmills.jpg" to nativeWidth * resizeFactor set the height of image "windmills.jpg" to nativeHeight * resizeFactor
Now, we could just insert this algorithm into our code, and then repeat it with slight variations to adjust the height, as needed. But we want to create a function that will be more useful in a general sense. So imagine a function that would take as input parameters the image dimensions and the maximum dimensions and return the new dimensions for both the height and the width of the image object. It might look something like this:
on mouseUp put the formattedWidth of image "windmills.jpg" into nativeWdth put the formattedHeight of image "windmills.jpg" into nativeHgt put the width of graphic "maxDim" into maxWdth put the height of graphic "maxDim" into maxHgt # here is the function call put constrainedDimensions(nativeWdth,nativeHgt,maxWdth,maxHgt) into newDimensions set the width of image "windmills.jpg" to item 1 of newDimensions set the height of image "windmills.jpg" to item 2 of newDimensions set the loc of image "windmills.jpg" to the loc of graphic "maxDim" end mouseUp ## this function handler would probably be in the stack or card script function constrainedDimensions oldW,oldH,maxW,maxH # check to see if width is different from the max width if oldW <> maxW then put maxW / oldW into resizeFactor put oldW * resizeFactor into newW put oldH * resizeFactor into newH end if # now check to see if the height is still too large if newH > maxH then put maxH / newH into resizeFactor put newW * resizeFactor into newW put newH * resizeFactor into newH end if # return the new dimensions return newW,newH end constrainedDimensions
While at first glance this may appear a little more complicated than the original code, notice that the new function is written in such a way that you could use it to proportionally resize any object to fit into an ideal-sized area. For example, if I wanted to fit a field into the area defined by graphic "maxDim", while still retaining the field's original height to width ratio, all I would have to do is to call the same function:
put constrainedDimensions(the width of fld "myFld",the height of fld "myFld",the width of grc "maxDim",the height of grc "maxDim") \ into newDims set the width of fld "myFld" to item 1 of newDims set the height of fld "myFld" to item 2 of newDims
A well-written function handler can save you work in the long run by allowing you to reuse code in different settings.
Used properly, functions are a great way to save yourself work by placing commonly-used calculations into a function handler. A well-written function should be abstract, generally-applicable, and placed high enough up in the message hierarchy that it can be accessed from any object that may need access to it.
1. Complete the Functions Exercise as outlined in the FunctionsExTemplate.rev stackfile (at http://dight310.byu.edu/lesson_materials/03_functions/FunctionsExTemplate.rev). Download your own copy from the Templates folder and rename it so your name is on the file.
2. Read LiveCode developer Geoff Canyon's article about functions. (Geoff's article was written several years ago, when LiveCode was called "Revolution".) While I don't agree with his ideas in every detail, it is an excellent discussion of how to write good functions and when to use functions rather than handlers. At the end of the article is a section called Postscript -- Examples where he gives some ideas for cases in which you might want to write a function. Take the third example, Quoting Text, and write a function in your Functions Exercise stack that will do what the example describes. On the last card at the end of your Functions Exercise stack create a demonstration of the use of this new function.
Turn it in to the homework drop folder when you're done. As always, the key to this exericse is in the Keys folder for your reference if you get stuck.