Notes on Array Cast: Tacit#5

I'll start by commenting on what was said at the at the end of the episode of Array Cast with the title Tacit#5: They implied that the episode was long, and people probably wouldn't make it to the end.

In case any of the hosts are reading this: I know it was mentioned that you didn't want to hear if anyone felt the show was too slort. I'm going to say it anyway: The show is too short. Please make it at least 4 hours.

Deriving a function vs. returning a function

In the episode, there was a discussion around the difference between deriving a function and returning a function. I think this distinction was harder to make in the context of APL, because of two main reasons: APL does not have a separate parse step, and it does not support first-class functions. This makes it harder to explain precisely what the difference is.

To state the difference clearly: Deriving a function is a parse-time or compile-time operation, while returning one is a runtime operation.

Since Kap has first-class functions in addition to APL-style operators, I will use it to illustrate the difference.

Consider the following code:

+/ 1 2 3 4

While the above expression is parsed, +/ is processed and the resulting code is a call to the reduction function. The reduction function is an actual function which is created by the parser when it sees the reduction operator. This function contains a reference to another function (+ in this particular case) which then represents the “reduction over sum”.

Note that all of this happens before any code has been evaluated. It's just part of the parser.

By contrast, let's take a look at how to return a function. Here is the definition of the function foo which accepts a value, and returns a function which multiples its argument with that value:

∇ foo (a) {
  λ{⍵×a}
}

The λ symbol is Kap-specific syntax, and is used to represent a function as a value. The lambda symbol should be followed by any function, and the entire expression is then a simple scalar value that can be processed like any other value: it can be returned from functions, it can passed to other functions, it can be put inside arrays, etc.

Here is how we can create an array of two functions:

b ← (foo 2) (foo 3)

Here we have two calls to foo, each of which returns a different function (the first multiplying its argument by 2 and the second by 3). This is where functions are returned.

In Kap, to be able to call a function that is represented as a value, you use the “apply” symbol: . This derives a function from an expression:

⍞(b[0]) 5

Note that since deriving a function is a parse-time operation, the derivation doesn't know what b[0] contains. Thus, the derived function references the expression itself, and it's only when the derived function is actually called that the inner expression is evaluated. This yields the function reference which can then be called.

Or, to put it in a different way, the code above is a derived function (which is derived at parse time from the apply symbol). At runtime, this function evaluates an expression (dereferencing the first element in the array b) which returns a function which is then called.

Mention of Kap

Kap was mentioned very briefly in the context of syntax for the fork. In J and Dyalog, a fork has the syntax (A B C) where all elements are functions. Without repeating 5 episodes worth of discussions around this topic (please listen to them, they're interesting) the following expression: x (A B C) y is evaluated as (x A y) B (x C y).

As mentioned on the Podcast, Kap has a special syntax for this which uses special symbols: x A«B»C y. The benefit of this is that the regular 2-element train extends naturally to 3 or more elements. This eliminates the “even vs. odd” issue that causes a lot of confusion to anyone learning tacit programming.