Most programming languages provide an initial set of procedures (primitives) and allow users to create new procedures. In some languages, all the primitives use special syntax, such as the infix arithmetic operators, and so it's not obvious that there are primitives. But there are. Typically, the primitives are implemented in some lower-level language—in the extreme case, machine language (in the form of assembler notation). So, pre-v10, Snap! primitives, for example, have been implemented in Javascript.
But there are very few primitives that can't be expressed in terms of other procedures. The others are primitive only because it makes the language run faster. As a trivial example, > can be defined in terms of <, =, and the Boolean operators AND, OR, and NOT.
An example that's much more significant in Snap! history is that the higher order list functions MAP, KEEP, and REDUCE can all be defined in terms of list selectors (e.g., ITEM 1 OF) and CALL; the latter and its sibling RUN are the basis of all higher order procedures in Snap!. We are very proud of the fact that we can write MAP and friends in Snap!, without recourse to Javascript code, because we have first class procedures. In our earliest versions (in BYOB), the HOFs were indeed library procedures implemented in Snap! itself. This was a great virtue pedagogically; students could see that functions of functions aren't magic, and you can write your own. But it made the HOFs slow. That wasn't so important until people started using Snap! to handle large amounts of data, including, in particular, costumes and sounds. So we made the HOFs primitive, at the cost of making them opaque.
But in v10, we take an idea from Smalltalk, which was entirely written in Smalltalk itself. So almost all primitives include an implementation in Snap! code, visible when you edit them. Here's COMBINE:
The gray PRIMITIVE block calls the Javascript implementation if the Boolean input at the left is true (and returns whatever it returns). If not, though, it actually runs the Snap! code that comes after it.
This gives you the best of both worlds; you can have super fast primitives, but you can also see how their algorithms work, and you can make your own modified versions if you choose. (E.g., the Snap! code shown here doesn't quite give the same answer as the Javascript primitive if the input list is empty:
whereas the Snap! code always reports zero, regardless of the function input, if the list input is empty. This isn't because Snap! can't do what the Javascript code does. So, as an exercise, write a correct Snap! implementation of COMBINE, which, if the input is a variadic block, calls it with no inputs when the list is empty.)
Pedagogically, there's a tradeoff between a correct implementation and a readable one. Arguably it's good that the Snap! implementation of COMBINE leaves out the code to check for that special case. In the case of MAP, though, a similar question led to the opposite solution:
This implementation handles the case of supplying implicit inputs VALUE, INDEX, and LIST to gray rings in the MAP function input. Arguably it'd be better pedagogy to show it as
Taking this argument a step further, note that the Snap! code for MAP in v10 doesn't really behave like the primitive, which distinguishes linked lists from dynamic arrays:
What's a linked list? What's a dynamic array? Read this:
I copied a list to another variable, and then when I change the copy, it changes the original too! - #13
And continuing our exploration one step further, I note gleefully that Jens, who pooh-poohs my annoyingly repeated requests for FOR I = 1 UPTO 10 on the grounds that nobody except me ever gets bitten by the fact that FOR (and NUMBERS FROM) counts backward, has that exact bug in his Snap! version of MAP:
:~P