What makes Pico special?

Pico can be seen as a modern language that combines the power and elegance of Scheme with the readability of "infix languages" like C or Java. Unifying these two characteristics was a non-trivial exercise and resulted in some original language features:

(for connoisseurs of Scheme: here is a paper and presentation that compares Pico with Scheme. It was presented at the first european Scheme and Lisp workshop).

Tables instead of Lists

Much of the power of Scheme is due to the fact that it is list-based. Data structures basically consist of lists. Furthermore Scheme programs are also stored and represented as lists. This unification of the basic data structures and abstract grammar is at the basis of much of the power of Scheme. Features like meta programming and a nice conceptual vision on functions of variable length arguments are direct consequences of the list orientation of Scheme. This is also supported by the implementation technology: the Scheme memory model is essentially list based, both in its abilities to store programs as in the way it stores data structures. In Pico this list-orientation was replaced by tables, i.e. arrays. Pico infix notation required us to think about fixed size data structures to store programs, i.e. tables. This philosophy was then consistently applied to the rest of the language: tables are used to store programs, tables are the basic data structures of the language, variable size argument lists are implemented by table parameter passing, the memory model and garbage collector are optimized to handle variable sized tables and meta programming consist of manipulating tables (i.e. the parse trees).

Infix Operators

One of the things we definitely wanted to get rid of during the design of Pico was Scheme's prefix operator notation system. For trained computer scientists and for "believers" this is considered to be a non-issue or even one of the features that engenders the power of Scheme. But in our experience it is also one of the things that scare people away. We therefore wanted to adopt the C style infix operator notation. However(!) this does not mean that operators are "special citizens" in Pico. On the contrary! In Pico, operators are simply functions whose name consist of a special kind of symbols (like +, -, *, & and so on). This lexical property allows operators to be defined and used in the conventional way. But from an implementational point of view they are just functions. It is the Pico parsing phase that converts operator definitions into plain function definitions, and that converts operator usage into plain function applications. The parser recognizes four different sets of operators: the additive operator set A, the multiplicative operator set M, the relational operator set R and the exponential operator set X. The parser guarantees that X > M > A > R. These sets are defined in the tutorial.

No Special Forms

Pico has a very uniform syntax without special keywords. This is the same as in Scheme. However, in Scheme the absence of keywords is covered by a new concept, called special forms. This is because Scheme features eager evaluation: arguments are always evaluated before a function is called. In Pico this need not be the case. Depending on the parameters of the function, arguments can be delayed. This allows Pico "keywords" to look exactly like function calls, and, in fact, they are just plain function calls. The result is an extremely regular language in which "predefined" functions play the role of keywords or special forms. But these functions are just ordinary functions that can be redefined. In order to understand this, one needs to understand the functional arguments explained below. It all boils down to the fact that functions can indicate that some parameters aren't "normal" parameters but are "functional parameters" instead. The evaluator will not evaluate arguments supplied to that kind of parameters.This is explained below in the section on functional arguments.

Functions with Variable Sized Arguments

As already said, Pico can be considered as an accessible Scheme. One of the consequences of opting for an infix notation and still trying to establish a one-to-one correspondence between the way programs look and the data structures that are used to store programs (i.e. the abstract grammar) is that the language essentially becomes table (i.e. array) driven instead of list driven. In Scheme, the data structures are lists and programs are presented as lists. In Pico, all data structures are tables and programs are stored as nested tables.

A fact not known by many people is that the arguments of a lambda is actually also a list. This means that the following two definitions are equivalent in Scheme:

(lambda (x y) (+ x y))
(lambda r (+ (car r) (cadr r)))

I.e., Scheme formal parameters are actually lists. This enables functions of a variable number of parameters as shown by the second example.

Transposing these ideas to a table driven language, we introduced a special notation for functions of a variable number of arguments:

f@arg: size(arg)

This function takes any number of arguments. Upon calling the function f(1,2,3), the arguments are evaluated from left to right. The resulting values are collected into a table of appropriate size and the table is passed to the function as arg. In the example given, the result of calling f(1,2,3) will be three since size(arg) is three since there are three arguments passed to f. The following shows how begin was implemented in Pico:

begin@args: args[size(args)]

begin can be called with any number of arguments. These will be evaluated from left to right (which is what we want) and will be collected in the table args. The body of begin returns the last of these arguments (which is what we want!).

Functional Arguments

In Pico, functions can have two kinds of parameters: normal parameters and functional parameters. The actual kind of a parameter is syntactically visible in the definition of the function. Binding actual arguments to formal parameters happens differently depending on the kind of formal parameter. In the case of normal parameters (i.e. the parameter is a normal name as in fac(n):if(n=0,1,n*fac(n-1)) ), the argument is evaluated and the associated value is bound to the formal parameter before the body of the function is evaluated. In order to describe the behaviour associated to functional parameters, it is best to look at an example:

zero(a, b, f(x), epsilon):{
   c: (a+b)/2;
   if(abs(f(c)) < epsilon,
      c,
      if(f(a)*f(c) < 0,
         zero(a, c, f(x), epsilon),
         zero(c, b, f(x), epsilon))) }

This Pico function looks for a zero of a function f(x) between a and b given a precision epsilon. The fact that f(x) is a functional argument results in the third expression (upon calling zero) never being evaluated. When calling zero with 4 arguments, the first, the second and the fourth will be evaluated. But the third argument will be taken as an expression (depending on x) to be used as the body for a newly defined function called f and of one parameter x. Hence, the way to call zero is zero(-1,1,x*2-5,0.001). The result is that inside zero, 4 names are accessible: a, b, f and epsilon. f happens to be bound to a function of one argument x (which is NOT visible inside zero) and body x*2-5.

This type of arguments allows us to extend Pico in itself since functional parameters cause their arguments to be delayed. The following code excerpt shows how the Pico while function is programmed in Pico itself. In the same way, true, false, and, or and if where implemented as built-in functions that happen to delay some of their arguments:

while(cond(),body()): {
  loop(value,pred):pred(loop(body(),cond()),value);
  loop(void,cond()) }

The while function assumes the Church encoding of booleans:

true(t(),f()):t()
false(t(),f()):f()

These examples show how functional parameters (of zero arguments in these cases) allow for automatic thunk creation upon calling a function. This is in contrast to languages like SmallTalk or Self where one has to manually delay arguments by wrapping them in a function:

expression ifTrue: [ ... ] ifFalse: [ ... ]

In languages like Scheme it is the name of the "function" (like if) that determines what will happen.

Conclusion

As C.A.R. Hoare indicates in his famous paper "Hints on Programming Language Design", there are two views on language design:

Pico is innovative (e.g. the special parameter passign that leads to an extensible language) but above all, Pico is the tangible result of a non trivial integrational gedankenexperiment. Pico integrates a set of uncommon language features into one simple consistent language framework.