======================================================= Writing Functions or Procedures ======================================================= Any moderately complex script will eventually include calcluations that need to be repeated. The preferred way to do this is write your own function or **procedure**, which can be called exactly as the built-in functions. For maximum clarity in this section we tend use the word **procedure** for a function written in Larch, leaving **function** to imply a Python function. In fact, as we will see, there is very little difference in practical use, and the rest of the documentation will be more lax about referring to functions written in Larch as functions. Once you're ready to write procedures, you'll almost certainly want to read about running Larch scripts and modules in the next section of this tutorial. Def statement ================= To define a procedure, you use the **def** statement, and write a block of code. This looks much like the **if**, **for**, and **while** blocks discussed earlier. A simple example would be:: def sayhello(): print('hello!') #enddef With this definition, one can then run this procedure as you would run any other built-in function, by writing:: larch> sayhello() hello! Of course, you can write procedures that take input arguments, such as:: def safe_sqrt(x): if x > 0: print sqrt(x) else: print('Did you want sqrt(%f) = %f?' % (-x, sqrt(-x))) #endif #enddef Here *x* will hold whatever value is passed to it, so that:: larch> safe_sqrt(4) 2.0 larch> safe_sqrt(-9) Did you want sqrt(9.000000) = 3.000000? Of course, you will most often want a procedure to return a value. This is done with the **return** statement. A **return** statement can be put anywhere in a procedure definition. When encountered, it will cause the procedure to immediately exist, passing back any indicated value(s). If no **return** statement is given in a procedure, it will return ``None`` when the procedure has fully executed. An example:: def safe_sqrt(x): if x > 0: return sqrt(x) else: return sqrt(-x) #endif #enddef which can now be used as:: larch> print(safe_sqrt(4)) 2.0 larch> x = safe_sqrt(-10) larch> print(x) 3.16227766017 **return** can take multiple arguments, separated by a comma, which is to say a **tuple**. As an example:: larch> def sum_diff(x, y): .....> return x + y, x-y .....> #enddef larch> print(sum_diff(3., 4.)) (7.0, -1.0) This is discussed in more detail below. The formal definition of a procedure looks like:: def (): #enddef .. _tut-namespaces-label: Namespace and "Scope" inside a Procedure ================================================= While inside a procedure, and important consideration is "what variables does this procedure have access to?". In fact, the question can be extended beyond procedures and functions to ask "what variables are available at any time during a Larch session or when a script is running?". This is known as **scoping**, and there is the notion of a **namespace** in which all the available variables exist. In Larch (to be clear, the rules here are slightly different than Python), every named object (variable, function, etc) exists in a Group. The top-level group is `_main`, and all groups descend from it. There is a special list ``_sys.searchGroupObjects`` holding the list of Groups to be searched for names. There are several related variables listed in :ref:`Table of Namespace-related Variables `. .. index:: _sys variables, namespace-related variables .. _namespace_table: Table of Namespace-related Variables Listed are the name of variables holding information used in the looking up of symbol names. ========================= ============================================= *variable* *content* ========================= ============================================= _sys.localGroup group for variables passed into or created in a procedure _sys.moduleGroup group for module-wide variables -- those definied in the same file as the current procedure. _sys.searchGroupObjects current list (ordered) of actual groups searched _sys.searchGroups current list of actual group names searched _sys.core_groups ('_main', '_sys', '_builtin', '_math') ========================= ============================================= `_sys.searchGroups` and `_sys.searchGroupObjects` are always kept in sync, and always contain the groups named in `_sys.core_groups`. In addition, they always contain (in order, if not ``None``), `_sys.localGroup`, `_sys.moduleGroup`. If not inside a function or module, `_sys.localGroup` and `_sys.moduleGroup` are set to `_main`. Thus, inside a procedure, the way names are looked up are: 1. First, variables defined in the procedure definition, passed in as arguments, those created inside the procedure. 2. Second, variable defined at the top-level (not inside other procedures) in the same module in which the procedure is defined. 3. Third, by searching through the list of other search groups, including all the groups in `_sys.core_groups`, and probably several others brought in from some plug-in. In principle, you can alter some of these variables in the `_sys` group. This is a really bad idea, and you should avoid doing it at all costs. The return statement, and multiple Return values ====================================================== As seen above, the **return** statement will exit a procedure, and send back a value to the calling code. The return value can be either a single value or a tuple of values, which gives a convenient way to return multiple values from a single procedure. Thus:: larch> def my_divmod(x, y): .....> return (x // y, x % y) # note use of // for integer division! .....> #enddef larch> print(my_divmod(100, 7)) 14, 2 But be careful when assigning the return value to variable(s). You can do:: larch> xdiv, xmod = my_divmod(100, 7) larch> print(xdiv) 14 or:: larch> result = my_divmod(100, 7) larch> print(result[0], result[1]) 14, 2 Because a return value from a procedure can hold many values, it is best to be careful when writing a procedure that you document what the return value is, and when using a procedure that you're getting the correct number of values. Keyword arguments ======================= For the procedures defined so far, the arguments have been both required and in a fixed order. Sometimes, you'll want to give a procedure optional arguments, and perhaps allow some flexibility in the order of the arguments. Larch allows this with **keyword** arguments. Keyword arguments offer distinct advantages over positional arguments in that they have default values, and can be given in any order. In a procedure definition, you add an argument name with a default value, like this:: def xlog(a, base=e): """return log(a) with base = base (default=e=2.71828...) """ if base > 1: return log(a) /log(base) else: print('cannot calculate log base %f' % base) #endif #enddef Unless passed in, the value of *base* will take the default value of *e*. This can then be used as:: larch> xlog(16) 2.7725887222397811 larch> xlog(16, base=10) 1.2041199826559246 larch> xlog(16, base=2) 4.0 You can supply many keyword arguments -- they can be given in any order, but they must all come *after* the positional arguments. A procedure can be written to take an unspecified number of positional and keyword parameters, using a special syntax for unspecified positional arguments and for unspecified keyword arguments. To use unspecified positional arguments, a procedure definition takes an argument preceded by a '*' after all the named positional arguments, like this:: def addall(a, b, *args): """add all (at least 2!!) arguments given""" out = a + b for c in args: out = out + c endfor return out enddef Here, the **'*args'** arguments means to use the variable 'args' to hold any number of positional arguments beyond those explicitly given. Inside the procedure, a tuple named 'args' will hold any positional parameters included in the call to 'addall' past the first two (which will be held by 'a' and 'b'). Thus, this procedure can be used as:: larch> addall(2, 3) # args = () 5 larch> addall(2, 3, 5, 7) # args = (5, 7) 17 To add support for unspecified keyword parameters, one adds a named argument to the procedure definition preceded by two asterisks: **'**keywords'**. For example:: def operate(a, b, **options): """perform operation on a and b""" debug = options.get('debug', True) verbose = options.get('verbose', False) op = options.get('op', 'add') if verbose: print('op == %s ' % op) #endif if op == 'add': return a + b elif op == 'sub': return a - b elif op == 'mul': return a * b elif op == 'div': return a / b else: if debug: print('unsupported operation!') #endif #enddef As you may have figured out, inside the procedure, 'options' will hold a dictionary of keyword names/values passed into it. With this (perhaps contrived) definition, you can call 'operate' many ways to change its behavior:: larch> operate(3, 2, op='add') 5 larch> operate(3, 2, op='add', verbose=True) op == add 5 larch> operate(3, 2, op='mul', verbose=True) op == mul 6 larch> operate(3, 2, op='xxx', verbose=True) op == xxx unsupported operation! larch> operate(3, 2, op='xxx', debug=False) op == xxx As with the **'*args'**, the **'**options'** in the procedure definition must appear after any named keyword parameters, and will not include the named keyword parameters. Documentation Strings ======================= It is generally a good idea to document your procedures so that you and others can read what it is meant to do and how to use it. Larch has a built-in mechanism for supporting procedure documentaion. If the first statement in a procedure is a **bare string** (that is, a string that is not assigned to a variable), then this will be used as the procedure documentation. You can use triple-quoted strings for multi-line documentation strings. This doc string will be used by the built-in help mechanism, or when viewing details of the procedure. For example:: def safe_sqrt(x): """safe sqrt function: returns sqrt(abs(x)) """ return sqrt(abs(x)) #enddef With this definition:: larch> help(safe_sqrt) safe sqrt function: returns sqrt(abs(x))