Functions and :functions
Functions and :functions
Functions are Lisp objects which can be called, or invoked, to
accept some inputs (or "arguments") and yield corresponding outputs or "return values."
Typically, functions will simply return new values without modifying
any of the inputs. If you program mostly with such a non-modifying
style, you are doing _Functional Programming.
GendL offers two kinds of functions:
- Functions defined using the defun macro, Sometimes called Named Functions, such functions become objects in memory which have a symbol for a name. Usually, variables mentioned within the body of such functions are "passed in" to the function call as arguments, Variables with Global and Dynamic Scope can also be accessed within function bodies, but we will cover those types of variables later.
- Functions defined within the :functions section of a GendL object definition. As with toplevel defun functions, variables may be passed into :functions as arguments and the function bodies may access
global data. But additionally, :functions can also access any other messages defined within
the same GendL object type where they occur. Messages can be any of
We have already encountered a number of Named Functions, for example
- + which returns the sum of the numbers supplied as arguments
- length which returns the length of a list supplied as an argument
Named functions defined using the defun macro use Prefix Notation. In other words, the function name is specified first, followed by its arguments. Named functions can accept
- Specific standard arguments, which immediately follow the function name
- Specific optional arguments, identified using &optional in the argument list
- Specific keyword arguments, identified using &key in the argument list
- Any number of remaining arguments, identified using &rest in the argument list
Here we define two functions with one and two arguments respectively. In the second function, the ordering of the arguments is what determines which input value ends up in which variable in the body of the function.
GDL-USER> (defun add2 (x) (+ x 2))ADD2
GDL-USER> (add2 4)6GDL-USER> (defun kinetic-energy (mass velocity)(div (* mass velocity velocity) 2))KINETIC-ENERGY
GDL-USER> (kinetic-energy 5 12)360.0
The syntax for optional arguments is
defun function-name (&optional (arg1-name arg1-default) (arg2-name arg2-default))
- In principle there is no limit to the number of optional arguments you can specify
- Optional arguments are order dependant as with Standard ones, so if arg2 is to be specified, arg1 must be specified as well.
GDL-USER> (defun kinetic-energy (&optional (mass 5) (velocity 12)(div (* mass velocity velocity) 2))KINETIC-ENERGY
GDL-USER> (kinetic-energy 10)720.0
GDL-USER> (kinetic-energy 10 24)2880.0
The first call to kinetic-energy passes in no values for the arguments and so it ends up using both default optional values. The second call uses a supplied value for mass but the default value for velocity. And the third call to kinetic-energy uses supplied values for mass and velocity. Note that when defined in this way, in order to used a specified velocity, then mass must be supplied, even if given simply as its default value
The syntax for keyword arguments is
defun function-name (&key (arg1-key arg1-default) (arg2-key arg2-deefault))
- As with Optional arguments, there is no limit to the number of keyword arguments you can specify
- Contrasted with Standard and Optional arguments, you can call a function with Keyword arguments with those argument values specified in any order (because each one is identified explicitly by a keyword symbol in the definition of the function. This is a significant advantage over Optional and Standard arguments, especially when you want to extend a function to accept new arguments without breaking existing code which is calling it.
- As with Optional arguments, you only need to specify the keyword arguments you want to use when calling the function, and any arguments which you do not include in the call will take on their default values when the body of the function is being evaluated.
GDL-USER> (defun kinetic-energy (&key (mass 5) (velocity 12)(div (* mass velocity velocity) 2))KINETIC-ENERGY
GDL-USER> (kinetic-energy :mass 10)720.0
GDL-USER> (kinetic-energy :velocity 24)1440.0
GDL-USER> (kinetic-energy :velocity 24 :mass 10)2880.0
Above we can see how the order-dependency is removed
Some general recomendations and observations:
- If there is only one argument then it's reasonable to use a Standard argument
- Optional arguments will be easy to work with if there is only one of them, a bit less easy if there are two, and will start becoming unwieldy to work with if you use more than 2 Optional arguments. Additionally, thought needs to be given to the order in which they are defined to avoid having to specify optional arguments just to occupy a space in the argument list (e.g. if you have two optional arguments, list first the one which is more likely to be passed in as a non-default value in calls to that function.
- Keyword arguments work well when the function has more than 1 argument, and help with readability
- Optional and Keyword arguments are relatively 'expensive' at runtime compared with Standard arguments, so particularly with Keyword arguments you may be trading Convenience for Speed in some cases (such cases can be shaken out with Runtime Profiling)
GendL object :functions
A GendL object function is defined in the :functions section of an object definition made with the define-object macro. The function only "exists" in the context of the object definition itself, but it can reference all of the slots within the object directly, rather than you having to pass in values as arguments. But of course it can also accept passed-in arguments. The argument syntax is identical to that for Named Functions (this style of argument list is called a "lambda list". The Named Function kinetic-energy shown above, when defined as a GendL object function, may look like this:
(defun kinetic-energy (&key (mass 5) (velocity 12)(div (* mass velocity velocity) 2)))(define-object function-example(base-object):computed-slots((mass 5)(velocity 12)(ke-1 (the kinetic-energy-1))(ke-2 (the kinetic-energy-2))(ke-3 (the (kinetic-energy-2 :mass 10)))(ke-4 (the (kinetic-energy-2 :velocity 24)))(ke-5 (the (kinetic-energy-2 :velocity 24 :mass 10)))(ke-6 (kinetic-energy :mass 10 :velocity 24))):functions((kinetic-energy-1 () (div (* (the mass) (the velocity) (the velocity)) 2))(kinetic-energy-2 (&key (mass (the mass)) (velocity (the velocity)))(div (* mass velocity velocity) 2))))
GDL-USER> (setq self (make-object 'function-example))#<GDL-USER::FUNCTION-EXAMPLE #x210397F77D>
GDL-USER> (the ke-1)360.0
GDL-USER> (the ke-2)360.0
GDL-USER> (the ke-3)720.0
GDL-USER> (the ke-4)1440.0
GDL-USER> (the ke-5)2880.0
GDL-USER> (the ke-6)2880.0
Some points to note from the above example
- The :function kinetic-energy-1 has no arguments but it references the :computed-slots mass and velocity directly using the the macro
- The :function kinetic-energy-2 defines keyword arguments for mass and veocity and sets their default values to the values of the :computed-slots mass and velocity.
- When a :function is called without arguments, the the referencing macro is still used but the :function does not need enclosing in parentheses (so such a reference would look just like a reference to a :computed-slot (see :computed-slots above)
- When a :function is called with arguments, whilst the the
referencing macro is again used, you must wrap the function name and
arguments with parentheses, similar to calling a normal Named function
with the only difference being an outer wrapping referencing macro e.g.
(the ... )(see the :computed-slots ke-3, ke-4 and ke-5
- Compare and contrast these :computed-slots with the way in which the kinetic-energy Named Function is used for ke-6