Quantcast
Channel: Analog/Custom Design
Viewing all articles
Browse latest Browse all 752

SKILL for the Skilled: Part 9, Many Ways to Sum a List

$
0
0
In the previous postings of SKILL for the Skilled, we've looked at different ways to sum the elements of a list of numbers. In this posting, we'll look at at least one way to NOT sum a list.

In my most recent posting, the particular subject was how to use SKILL++ to define a make_adder function. I commented in that article that the same thing would not work in traditional SKILL. In this posting, I'd like to walk through some of the issues you would encounter if you actually tried to implement this in traditional SKILL. Understanding why it does not work in the traditional SKILL dialect may shed some light on why it does work in the SKILL++ dialect.

Remember, these are not bugs in any sense in SKILL. The fact simply is that in some subtle ways traditional SKILL and SKILL++ behave differently. You can exploit the differences to your advantage, depending on your particular application.

Recall, here is the definition of make_adder_8a as you might type it into the CIWindow.

(unless (theEnvironment)
   (toplevel 'ils))

(defun make_adder_8a ()   ; 1.1
  (let ((sum 0))          ; 1.2
    (lambda (n)           ; 1.3
      sum = sum + n)))    ; 1.4

Try it in traditional SKILL

What happens if you try to define the make_adder_9a in traditional SKILL? Here is what will happen when you try to call the function.
(when (theEnvironment)
  (resume))

(defun make_adder_9a ()  ; 2.1
  (let ((sum 0))         ; 2.2
    (lambda (n)          ; 2.3
      sum = sum + n)))   ; 2.4

You see immediately that something very subtle is different.

C = (make_adder_9a)  ==> funobj@0x1e666390

The make_adder_9a function, when defined in traditional SKILL, returns an object which is printed something like funobj@0x1e666390. Contrast that with the SKILL++ version of the function make_adder_8a defined above, which returned an object which was printed as funobj:A. What happens if we try to call the function we just created?

(C 1)
*Error* eval: undefined function - C<<< Stack Trace >>>
C(1)
A drastic difference is obvious when you try to call the function, C. You have an error message indicating that C is not a function. This can be confusing to the beginner SKILL++ programmer. The reason for this is that the CIWindow is in SKILL listening mode. A similar thing happens if loading code from a file with a .il file name extension as opposed to a .ils extension.

For a discussion of changing the CIWindow listening mode between SKILL and SKILL++, see SKILL for the Skilled: Part 8, Many Ways to Sum a List, or consult the Cadence SKILL documentation for the toplevel and resume.

Setting a variable in SKILL

When the CIWindow is in SKILL listening mode an expression such as
C = (make_adder_9a)
modifies (or initially defines) a global variable named C to be the value returned from the make_adder_9a function. An expression such as
(defun C ()
  (println '(1 2 3 4)))
defines a global function named C. Critically important to how traditional SKILL works is that the variable C and the function C are different and independent. Either one can be referenced, or modified independent of the other. This is both a feature of SKILL as well as a point of confusion.

In SKILL mode the following expressions are semantically equivalent in setting the value of the variable C.

C = (make_adder_9a)
(setq C (make_adder_9a))
(set 'C (make_adder_9a))

The following expressions are equivalent in defining the function C.

(defun C ()
  (println '(1 2 3 4)))

(procedure (C)
  (println '(1 2 3 4)))

(putd 'C
  (lambda ()
    (println '(1 2 3 4))))

Setting a variable in SKILL++

By contrast when the CIWindow is in SKILL++ mode, an expression such as A = (make_adder_8a) (shown above) not only sets the global variable named A, but also defines a global function named A. If you then proceed to evaluate an expression such as
(defun A ()
  (println '(1 2 3 4)))

the global variable, A and the global function A will be modified to something new, and the old value will be lost (unless of course you have saved a reference to it).

SKILL++ does not support having a global variable and global function of the same name with different values.

In SKILL++ mode all the following expressions are equivalent in setting the global variable and simultaneously defining the global function named C.

(defun C ()
  (println '(1 2 3 4)))

(procedure (C)
  (println '(1 2 3 4)))

(define (C)
  (println '(1 2 3 4)))

(define C
  (lambda ()
    (println '(1 2 3 4))))

(setq C
  (lambda ()
    (println '(1 2 3 4))))

(putd 'C
  (lambda ()
    (println '(1 2 3 4))))

LISP-1 vs LISP-2

Another way to think about the different semantics of variables and functions between SKILL and SKILL++ is that SKILL++ is a LISP-1 and traditional SKILL is a LISP-2. LISP-1 means that there is one single name space for variables and functions. LISP-2 means that there are two different names spaces, one for variables and one for functions.

Use funcall to call a function indirectly

To call a function, in SKILL mode, whose value is stored in a variable (global or otherwise), you need to use funcall. If you use funcall to call the function whose value is in the global variable D, the function is indeed called but quickly encounters an error.
D = (make_adder_9a)  ==> funobj@0x1e666390
(funcall D 1)
*Error* eval: unbound variable - sum<<< Stack Trace >>>
(sum + n)
(sum = (sum + n))
funobj@0x1e666390()
funcall(D 1)

For some reason, the definition of the adder created by make_adder_9a, tries to reference a global variable named sum. Let's ignore this error momentarily, and assume we already accidentally had a global variable named sum whose value happened to be 0.

sum = 0  ==> 0
D = (make_adder_9a)  ==> funobj@0x1e666390
(funcall D 1)  ==> 1
(funcall D 2)  ==> 3
(funcall D 3)  ==> 6
(funcall D 4)  ==> 10
(funcall D 5)  ==> 15

This requirement of having to use funcall when calling a function indirectly is inherent to the way traditional SKILL works.

From the above experiment it deceptively seems that the SKILL function make_adder_9a behaves much the same as make_adder_8a with the exception of the calling syntax: (funcall D 1) vs (A 1).

A second SKILL adder fails

In the previous post, we used make_adder_8a to define adders B1, B2, and B3. We saw that all three adders kept track of their partial sum independently. What happens if we try to define even one additional adder using make_adder_8b?
E = (make_adder_9a)  ==> funobj@0x1e666390
(funcall E 1)  ==> 16
(funcall E 2)  ==> 18
(funcall E 3)  ==> 21

Hey wait! Why did (funcall E 1) return 16? Let's set the global sum back to 0 and try it again.

sum = 0  ==> 0
(funcall E 1)  ==> 1
(funcall E 2)  ==> 3
(funcall E 3)  ==> 6

OK, setting sum back to 0 fixes it. Now, look again at D.

(funcall D 1)  ==> 7
(funcall E 2)  ==> 9
(funcall D 3)  ==> 12

It seems that D and E are both using the same variable to keep track of the partial sum. In fact, if you print the value of the variable sum you'll see that its value is now 12.

Why does this fail?

In SKILL mode, the two adders, D and E, created by two different calls to make_adder_9a do not create two independent adders as did make_adder_8a in SKILL++ mode. The reason for this is critical to an important difference between SKILL and SKILL++. Look at lines 1.2, 1.3, and 1.4 of make_adder_8a and 2.1, 2.3, and 2.4 of make_adder_9a. While these lines are textually identical, they are semantically different in that make_adder_8a is SKILL++ and make_adder_9a is traditional SKILL.

Low level details of calling a SKILL function

Suppose make_adder_8a is called twice on line 3.1 and 3.4. Each time it is called, line 1.2 allocates a new binding which associates the variable sum with a location to store the value. The two functions created on line 1.3 each time make_adder_8a is called (lines 3.1 and 3.2), reference those respective bindings. You can see from line 3.3, that each time make_adder_8a is called, a different function is returned.

The textual reference to sum on line 1.4 references and modifies the value in the location of binding depending on which of the functions it is running from. When lines 3.4, and 3.5 are evaluted, line 1.4 references and modifies the first binding which was created as a result of evaluating line 3.1. Likewise, when lines 3.6, and 3.7 are evaluted, the same line 1.4 references and modifies the second binding which was created as a result of evaluating line 3.2.

A = (make_adder_8a)      ; 3.1  ==> funobj:A
B = (make_adder_8a)      ; 3.2  ==> funobj:B
(eq A B)                 ; 3.3  ==> nil
(A 1)                    ; 3.4  ==> 1
(A 2)                    ; 3.5  ==> 3
(B 11)                   ; 3.6  ==> 11
(B 22)                   ; 3.7  ==> 33

Low level details of calling a SKILL++ function

Constrast that with the very different scenaraio which what happens in make_adder_9a. In this case when make_adder_9a is called (lines 4.1 and 4.2), each time a binding is created (because the let on line 2.2 is evaluated twice), but the local function created on line 2.3 DOES NOT any explicitly reference it. In fact both times make_adder_9a is called (lines 4.1 and 4.2), the exact same function is returned as seen on line 4.3.

When make_adder_9a returns the SKILL local function created on line 1.3, the call to make_adder_9a returns and all references to the binding created on line 2.2 are subsequently lost. Thereafter, when the function D or E is called, SKILL (at calling time) searches for the currently defined variable named sum which is global.

D = (make_adder_9a)      ; 4.1  ==> funobj:@0x1e666390
E = (make_adder_9a)      ; 4.2  ==> funobj:@0x1e666390
(eq D E)                 ; 4.3  ==> t
(D 1)                    ; 4.4  ==> 1
(D 2)                    ; 4.5  ==> 3
(E 11)                   ; 4.6  ==> 11
(E 22)                   ; 4.7  ==> 33

Calling the functions stored in D and E (on lines 4.4 through 4.7) have much the same effect as directly evaluating a call to a copy of the inline function shown on lines 1.3 and 1.4. For example, lines 4.5 and 4.6 (D 2) and (E 11), are equivalent to the following which has the effect of referencing and modifying the global variable named sum.

(funcall (lambda (n) sum = sum + n)
         2)
(funcall (lambda (n) sum = sum + n)
         11)

Summary

In this article we looked at several differences between SKILL and SKILL++.
  1. funcall -- How to call functions indirectly.
    • SKILL -- You must use funcall: (funcall C 1)
    • SKILL++ -- You may call the function directly: (A 1)
  2. name-space --
    • SKILL -- functions and variables live in different names-spaces; the function C and the variable C are different.
    • SKILL++ -- supports a single name-space; function A and the variable A are the same.
  3. closures -- how free variables are handled
    • SKILL -- does not support closures; a free variable is resolved at run-time and may resolve differently depending on how a function is called.
    • SKILL++ -- supports closures ; a free variable is resolved at compile time, and refers to the same allocated location no matter how the functions references them are called.

Feedback

Please let me know if you find any of the discussion or techniques discussed in this or previous articles useful.

Thanks for reading!

Jim Newton 


Viewing all articles
Browse latest Browse all 752

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>