In this episode of SKILL for the Skilled I'll introduce a feature of the
let primitive that Scheme programmers will find familiar, but other readers may have never seen before. The feature is called named let, and I'll show you how to use it to sum the numbers in a given list.
Named LETThere is a feature of
let available in SKILL++ which is not available in traditional SKILL, called named let. Here is an example of how it can be used to sum a given list of numbers.
(defun sumlist_7a (numbers)
(REPEAT (plus sum_so_far (car rest))
In this example, the named let defines a local function named,
REPEAT and calls it once with the two arguments,
numbers. Of course,
REPEAT is by no means a reserved word; you can name the local function any valid variable name. Within the body of the named let you may call the local function with two arguments. You may recursively call the function zero, one, or more times as your algorithm requires.
Testing the functionIf we enable tracing of
REPEAT, we can see what happens when calling
sumlist_7a. We can see that the local function,
REPEAT is called recursively several times, and that the implementation does in fact return the correct sum of the given list of numbers.
(sumlist_7a '(1 2 3 4 5))
|sumlist_7a((1 2 3 4 5))
||(REPEAT 0 (1 2 3 4 5))
|||(REPEAT 1 (2 3 4 5))
||||(REPEAT 3 (3 4 5))
|||||(REPEAT 6 (4 5))
||||||(REPEAT 10 (5))
|||||||(REPEAT 15 nil)
|||||||REPEAT --> 15
||||||REPEAT --> 15
|||||REPEAT --> 15
||||REPEAT --> 15
|||REPEAT --> 15
||REPEAT --> 15
|sumlist_7a --> 15
Equivalent to labels
The named let is more or less equivalent to a declaration and call of a local function as if by using
labels. If you recall, this is exactly what was shown in
sumlist_3b in SKILL for the Skilled: Many Ways to Sum a List (Part 3).
(defun sumlist_3b (numbers)
(labels ((sum (sum_so_far rest)
(sum (plus (car rest) sum_so_far)
(sum 0 numbers)))
If you trace
sum you'll see that it executes pretty much the same thing as
(sumlist_3b '(1 2 3 4 5))
|sumlist_3b((1 2 3 4 5))
||(sum 0 (1 2 3 4 5))
|||(sum 1 (2 3 4 5))
||||(sum 3 (3 4 5))
|||||(sum 6 (4 5))
||||||(sum 10 (5))
|||||||(sum 15 nil)
|||||||sum --> 15
||||||sum --> 15
|||||sum --> 15
||||sum --> 15
|||sum --> 15
||sum --> 15
|sumlist_3b --> 15
Illusion of jumping to the top
The illusion (or abstraction) presented by the named let is that of being able to jump back to the top of the
let form, and evaluate it again with different initialization values.
Consider this simple
(println (list a b))
(when (plusp a)
(loop (sub1 a)
Which prints the following output.
Doesn't work in traditional SKILL
If you try to evaluate a named let in traditional SKILL (e.g., with a .il file extension), you'll get an error something like the following, which basically means that SKILL let expects a list as its first operand and you have given the symbol
*Error* let: local bindings must be a proper list - loop
let is syntactic sugar for lambda
In SKILL++ the normal let has the same semantics as calling an unnamed function with particular parameter values. For example:
(let ((a X)
(expr1 a b)
(expr2 b c))
Is semantically the same as the following arguably less readable expression. The expression uses funcall to call a nameless function, defined by the
(lambda (a b c) ...); in particular to call it with the three values
Z. In fact the two code snippets are simply syntactical transforms of each other.
(funcall (lambda (a b c)
(expr1 a b)
(expr2 b c))
When you look at the equivalent
lambda form of
let it is immediately clear that the expressions within the lambda are not able to make recursive calls to this unnamed function. This limitation is solved by the named let.
Named let and tail-call optimization
The illusion of jumping back to the top in sort of a
goto fashion is indeed what happens if tail-call-elimination is enabled via the optimizeTailCall status flag explained in SKILL for the Skilled: Many Ways to Sum a List (Part 4).
In this post I've shown some examples of how to used the named let construct of SKILL++. This construct converts the conventional
let into a loop --- a loop which can be repeated by calling the label as a function, providing the next iteration's variable values.
More to come
In upcoming posts we'll continue to survey the SKILL++ language using the example of summing a list.
SKILL for the Skilled: Many Ways to Sum a List (Part 3)
SKILL for the Skilled: Many Ways to Sum a List (Part 4)
Scheme In particular see the discussion of named let