Understanding the 'later' function
Dissecting the reference example
(prog1 # Parallel background calculation of square numbers (mapcan '((N) (later (cons) (* N N))) (1 2 3 4)) (wait NIL (full @)) )This call to 'prog1' executes two expressions, returning the result of the first one, but after executing the second one.
The first expression calls 'mapcan' on the list (1 2 3 4), by applying the function
((N) (later (cons) (* N N)))to each of the numbers. This function in turn returns the result of 'later'.
So the key here is 'later'. As the reference says,
(later 'var . prg) -> var'later' accepts a 'var' (i.e. a symbol or a list cell), and returns that var. As a side effect, it takes a program (a 'prg' which is a list of expressions), runs that in a child process, and stores the result of that 'prg' in the 'var' at some later point when the child process is done.
For a simple example, we can execute in the REPL
: (later 'A (+ 1 2 3 4 5)) -> AWe see that 'later' returs the variable 'A'. But if we look a moment after that into the value of 'A'
: A -> 15we see that it has received the result of the calculation. A single call to 'later' is not very useful, however, because we might instead directly execute the expression.
But if one same expression has to be done in parallel (a typical use case is starting a parallel database query on remote machines), then we pass list cells instead of a single variable to 'later'.
This is done with the 'mapcan' and 'cons' above. Remember that (cons) is just a shortcut of (cons NIL NIL), and that 'mapcan' concatenates the returned results. Thus, the above call without 'later' gives:
: (mapcan '((N) (cons NIL NIL)) (1 2 3 4)) -> (NIL NIL NIL NIL)However, if we have
(mapcan '((N) (later (cons) (* N N))) (1 2 3 4))Then as a side effect each call to 'later' receives its private, newly 'cons'ed list cell - i.e. a 'var' - which it duefully returns to 'mapcan' to be concatenated to the full list. At the same time, each 'later' remembers its private cell, and stores its result from the child process there. Then 'wait' waits until ALL cells are filled (i.e. non-NIL).
Another confusing thing is the @. How does one find which of the previous functions updated the @, and which did not?This is documented in The PicoLisp Reference. Exclusively the (flow) functions listed there will modify @.
There's also a nice article by Thorsten: The many uses of @ in PicoLisp
To be precise: The value of @ is the result of 'mapcan', i.e. the list of newly 'cons'ed cells:
(prog1 (mapcan '((N) (later (cons) (* N N))) (1 2 3 4)) (wait NIL (full @)) )However, it is not 'mapcan' that modifies @, but 'prog1'. 'prog1' is one of the above "Functions with controlling expressions".
So in the example, 'full' receives the list (NIL NIL NIL NIL). It looks at it repeatedly, until all 'NIL's are replaced with numeric results. This works because 'later' operates on a 'var', i.e. it remembers the cell and writes the result from the child's pipe into it.
Perhaps two more explicit variations of the above example make things more clear, not regarding @, but the interplay of the variables in 'later' and 'wait'. The following three expressions return the same result (1 4 9 16):
(prog1 (mapcan '((N) (later (cons) (* N N))) (1 2 3 4)) (wait NIL (full @)) ) (use (A B C D) (later 'A (* 1 1)) # We hope to have a value later in 'A' (later 'B (* 2 2)) # We hope to have a value later in 'B' (later 'C (* 3 3)) # We hope to have a value later in 'C' (later 'D (* 4 4)) # We hope to have a value later in 'D' (wait NIL (and A B C D)) (list A B C D) ) (let L (list NIL NIL NIL NIL) (later L (* 1 1)) # We hope to have a value later in the first cell (later (cdr L) (* 2 2)) # We hope to have a value later in the second cell (later (cddr L) (* 3 3)) # We hope to have a value later in the third cell (later (cdddr L) (* 4 4)) # We hope to have a value later in the fourth cell (wait NIL (and # An explicit variant of (full L) (car L) (cadr L) (caddr L) (cadddr L) ) ) L )