Dec 14th, 2012

S-expressions: Special forms

(sexpr  lexer  reader  eval  forms  special-forms  macros  walker  meta-eval)

Note: The terminology of this post was changed from special form to special operator where appropriate on Dec 28, 2012.

Ilias is quite powerful at this point. It allows arbitrary variables to be defined in PHP. These variables can be functions. These functions can be called from within Lisp.

However, there are some fundamental constructs that are missing to make the language unstoppable, which once added will inevitably lead to world domination: define, lambda and if.

Program

Before we look into that, let's simplify the usage of the toolchain. It now includes: Lexer, Reader, FormTreeBuilder. How about a class that packages those steps up. I will call it Program:

namespace Igorw\Ilias;

class Program
{
    private $lexer;
    private $reader;
    private $builder;

    public function __construct(Lexer $lexer, Reader $reader, FormTreeBuilder $builder)
    {
        $this->lexer = $lexer;
        $this->reader = $reader;
        $this->builder = $builder;
    }

    public function evaluate(Environment $env, $code)
    {
        $tokens = $this->lexer->tokenize($code);
        $ast = $this->reader->parse($tokens);
        $forms = $this->builder->parseAst($ast);

        $value = null;
        foreach ($forms as $form) {
            $value = $form->evaluate($env);
        }
        return $value;
    }
}

It lexes, then parses, then builds a form tree. Finally it evaluates all forms and returns the result of the last one.

Usage:

$program = new Program(
    new Lexer(),
    new Reader(),
    new FormTreeBuilder()
);

$env = Environment::standard();
var_dump($program->evaluate($env, '(+ 1 2)'));

That's a lot easier to use. Back to world domination.

Why special is special

The new constructs define, lambda and if are special. They are language constructs that do not behave like regular functions, although they may appear so.

define is used to assign a value to a symbol in the environment.

(define foo 42)
foo

That's a program of two forms. The first form is a define special form that defines the symbol foo to represent the value 42. The second form is a symbol form representing the foo variable that evaluates to 42. The return value of this program is 42.

The special thing about define is that it has access to the environment at call time. It needs to, in order to set the value on it. Normal functions do not have this capability.

The second, more profound difference which is very subtle in this case is that it does not evaluate all of its arguments. It surely evaluates the second argument, which is a literal. But it does not evaluate the first argument, the symbol.

If it tried to evaluate foo that would result in an error, since foo is not defined on the environment yet, as that's what define is about to do!

Special operator interface

In order to deal with special forms, they need to be modeled in the form tree. A special form is a list form whose first element is a special operator. The SpecialOp interface gives the operator access to the environment and to the unevaluated arguments.

I repeat, the unevaluated arguments.

namespace Igorw\Ilias\SpecialOp;

use Igorw\Ilias\Environment;
use Igorw\Ilias\Form\ListForm;

interface SpecialOp
{
    public function evaluate(Environment $env, ListForm $args);
}

The form tree builder does not have to be modified. Special operators are simply values defined on the environment that look like functions but have a different behaviour when evaluated. The only adjustment needs to be made in ListForm::evaluate(), since special forms are a special kind of list form.

public function evaluate(Environment $env)
{
    $func = $this->car()->evaluate($env);

    if ($func instanceof SpecialOp) {
        return $func->evaluate($env, $this->cdr());
    }

    $args = $this->evaluateArgs($env, $this->cdr());
    return call_user_func_array($func, $args);
}

If the function in an list form evaluation is a special form, it is evaluated with the cdr passed in unevaluated.

Define

define will be the first special form, implementing the SpecialOp interface:

namespace Igorw\Ilias\SpecialOp;

use Igorw\Ilias\Environment;
use Igorw\Ilias\Form\ListForm;

class DefineOp implements SpecialOp
{
    public function evaluate(Environment $env, ListForm $args)
    {
        $name = $args->car()->getSymbol();
        $env[$name] = $args->cdr()->car()->evaluate($env);
    }
}

It's trivial. The first argument is a symbol representing the name. Instead of evaluating it, the symbol is fetched and used as a key.

The second argument is a form whose evaluation is assigned to the environment. It is fetched by getting the car of the cdr of the arguments.

To make define available to the world, it just needs to be part of the standard environment:

namespace Igorw\Ilias;

class Environment extends \ArrayObject
{
    public static function standard()
    {
        return new static([
            'define'    => new SpecialOp\DefineOp(),

            '+'         => new Func\PlusFunc(),
        ]);
    }
}

That's it, the define special operator is now available, so this should work:

(define foo 42)
foo

Lambda

The term lambda describes an anonymous function. It originally was introduced as part of lambda calculus, a system for representing computation through functions.

In Lisp, lambda is a special operator that represents a function as a value. All functions are anonymous, the only way of naming them is by assigning them to a variable using define.

Here is a lambda with no arguments that always returns 42:

(lambda () 42)

It could be invoked directly without giving it a name, yielding the return value:

((lambda () 42))

And defined as the-answer, it can be called using that name:

(define the-answer (lambda () 42))
(the-answer)

Here is the identity function, it returns any argument it receives:

(define identity (lambda (x) x))

Here is an increment function, it returns the increment of the argument it receives:

(define increment (lambda (x) (+ x 1)))

The first argument to the lambda special operator is a list of symbols representing argument names. All the following arguments are forms to be evaluated when the function gets called, in the context of that function.

namespace Igorw\Ilias\SpecialOp;

use Igorw\Ilias\Environment;
use Igorw\Ilias\Form\ListForm;

class LambdaOp implements SpecialOp
{
    public function evaluate(Environment $env, ListForm $args)
    {
        $symbols = $args->car()->toArray();
        $argNames = $this->getMappedSymbols($symbols);

        $bodyForms = $args->cdr()->toArray();

        return function () use ($env, $argNames, $bodyForms) {
            $subEnv = clone $env;

            $vars = array_combine($argNames, func_get_args());
            foreach ($vars as $name => $value) {
                $subEnv[$name] = $value;
            }

            $value = null;
            foreach ($bodyForms as $form) {
                $value = $form->evaluate($subEnv);
            }
            return $value;
        };
    }

    private function getMappedSymbols(array $symbols)
    {
        return array_map(
            function ($symbol) {
                return $symbol->getSymbol();
            },
            $symbols
        );
    }
}

What is significant is that the function produced by lambda evaluates its body using a separate environment. The result of the last body form's evaluation is returned from the function.

After adding the new lambda special operator to the environment it should be able to do all those things. According to lambda calculus we can stop now, as anything can be represented using lambdas alone. But we will continue nevertheless.

If

Finally, the if special operator takes three arguments: a predicate, a true- form and an else-form. The predicate is evaluated. If the result of that evaluation is truthy, then the true-form is evaluated. If it is falsy, the else-form is evaluated. The result of the evaluated form is returned.

This is what an example usage looks like:

(if (= answer 42) 'correct 'wrong)

The implementation:

namespace Igorw\Ilias\SpecialOp;

use Igorw\Ilias\Environment;
use Igorw\Ilias\Form\ListForm;

class IfOp implements SpecialOp
{
    public function evaluate(Environment $env, ListForm $args)
    {
        $predicate = $args->car();
        $trueForm  = $args->cdr()->car();
        $elseForm  = $args->cdr()->cdr()->car();

        $form = ($predicate->evaluate($env)) ? $trueForm : $elseForm;
        return $form ? $form->evaluate($env) : null;
    }
}

Easy.

After adding it to the standard environment, it is available from within Lisp.

Fibonacci

Now that all of the pieces are in place it should be possible to use them. One way to demonstrate that is by implementing a function that calculates the fibonacci sequence.

I know, it's a lame example. But is based on conditional recursion which means it is a good test for scoping, naming and lazy evaluation of conditionals.

This is the (terribly inefficient) implementation of fib in Lisp:

(define fib (lambda (n)
    (if (< n 2)
        n
        (+ (fib (- n 1)) (fib (- n 2))))))

As you can see, I have added a - in addition to the + function.

And now, for the very first time in history will we witness special forms...

*drumroll*

(fib 23)

And it takes 4 seconds to compute the result which is...

28657, which is correct! Now anything is possible. The future awaits us!

Source code: Ilias

Conclusion

world domination


(sexpr  lexer  reader  eval  forms  special-forms  macros  walker  meta-eval)

Igor

Brought to you by @igorwhiletrue.

Projects you may be interested in: Silex, Stack, YOLO, React.