Functional Aspects of Nu

Friday October 5, 2007

Two topics appeared on the mailing list that drew out some of the functional aspects of Nu. I think they're worth discussing here.

Currying

Currying is a common functional programming technique for converting a function of a list of parameters into a function of a shorter list of parameters.

After a brief discussion of currying on the mailing list, the following example emerged to illustrate currying with Nu:

;; The curry function takes a function and a single argument 
;; and returns a new function that is the original function with 
;; its first parameter replaced by the specified argument.
(function curry (f arg)
    (set remaining-parameters ((send f parameters) cdr))
    (eval (list 'do remaining-parameters 
                 (append (list f arg) remaining-parameters))))

;; This example function just returns the sum of three numbers
(function add-triple (x y z) (+ x y z))
(puts (add-triple 5 4 3))

;; curry a 2-parameter function
(set add5-to-pair (curry add-triple 5))
(puts (add5-to-pair 4 3))

;; curry a 1-parameter function
(set add9-to-value (curry add5-to-pair 4))
(puts (add9-to-value 3))

;; curry a 0-parameter function
(set twelve (curry add9-to-value 3))
(puts (twelve))

Currently in Nu, this example only works for functions, because only functions are able to respond to the parameters message.

Operators such as +, -, etc. must be handled differently because they accept a variable number of arguments and have no well-defined parameter list.

This also raises the issue that there is no way yet to define functions accepting a variable number of arguments in Nu, but I'll gladly adopt something from existing Lisp or Scheme conventions.

Variable Scope

The other discussion was on the subject of variable scope:

I've recently run into a problem with `do` blocks and I don't think
that the behavior is correct.  The operators documentation says:
"Most of the time, the evaluation of code in a block takes place in
the context that was saved when the block was created."  If I have a
variable outside of a `do` block that I try to set in the block, it
isn't the set value in subsequent iterations or after the block
exits.  I can use "$" to denote variables as global but I'd prefer
objects to be limited to their scope.

Sincerely,
Grayson Hansard

       (set x 0)
       ('(1 2 3 4 5) each:(do (n) (set x (+ x n))))
       (puts x) ; Outputs 0; Expected 15 (1+2+3+4+5).

       (set l (NSMutableArray array))
       ('(1 2 3 4 5) each:(do (n) (l addObject:n)))
       (puts (l list)) ; Outputs "(1 2 3 4 5)";  Works as expected.

Grayson's observations about blocks were spot on (the documentation he quoted is here). What he described is a result of Nu's block scoping rules. In his example, x and l are names that are defined in a context that exists outside the block definitions. When the blocks are created with do, these names and their assigned values are kept with the blocks for use when the blocks are evaluated.

In the first case, the name x is given a new value inside the block. This new value assignment exists only in the context of the block and is not made in any outside context, so the value of x outside the block is unchanged, and the value for x for other evaluations of the block is also unchanged. It might not be obvious yet, but those other block evaluations may be occurring in parallel.

In the second case, the value associated with the name l is unchanged inside the block. Instead the block is making changes to an array object, and since there is only one array object, those changes are visible outside the block and in other evaluations of the block. Parallel evaluation of this block would be a problem.

So while the behavior seems surprising at first, it's what I prefer for Nu. It is conceptually simpler and it makes it more feasible to write parallelizing methods such as this:

(arrayOfTasks eachInSeparateThread: (do (task) ... ))

But if you really must have the other behavior, there's a way to get it. It's illustrated in the example below. For more detail, please look at the class documentation for the NuReference class.

(set y ((NuReference alloc) init))
(y setValue:0)
('(1 2 3 4 5) each:(do (n) (y setValue:((+ (y value) n)))))
(puts (y value))

As you'll see in the class documentation, this isn't the reason that I created the NuReference class, but as I heard Jim Coplien say at a C++ conference many years ago:

All interesting problems in computer science reduce to what's in a name, and can be solved by one more level of indirection.

Update

This question of variable scope is now resolved. Enough people argued convincingly for the other interpretation that beginning in Nu-0.2.0, closures are now made on variable bindings rather than values.

More details are in this post, titled Closures in Nu.