So far we looked at simple expressions and statements. This time we'll talk about constructs for describing functions / lambdas. ToyScript language supports simple functions. Consider rather trivial ToyScript code:

def f(a) {
    var x = "Hello"
    print x
    print a
}

f("ToyScript")

The function declares a local variable "x", performs rather unnecessary assignment and prints the value of the local and that of the parameter. We are consciously staying away from expressions because those would quickly get us into the exploration of dynamic behavior (ToyScript is a dynamic language after all). We'll get to it shortly, but for now the focus is on functions and how languages can express those in the context of DLR.

Running the code with -X:ShowASTs will output following two trees / code blocks. The first one is the top level code (function definition followed by the call to the function just defined). The second tree is the body of the ToyScript function "f" itself. We'll explore them in reverse of the print order, the body of the "f" first:

//
// CODE BLOCK: f (1)
//

.codeblock Object f ()(
    .arg Object a (Parameter,InParameterArray)
) {
    .var Object x (Local,InParameterArray)
    {
        (.bound x) = (Object)"Hello";
        (ToyHelpers.Print)(
            (.bound x),
        );
        (ToyHelpers.Print)(
            (.bound a),
        );
    }
}

Very little surprise here. We see what look like a function (.codeblock) called "f" which takes an argument "a" of type object and returns object as well. In addition to what we know from earlier (calls to ToyHelpers.Print), there's a local variable "x" in the DLR Tree and an assignment to it.

To represent the function body, the ToyScript creates a DLR CodeBlock using the Ast.CodeBlock factory. This happens in the ToyScope constructor, but the initial impulse comes from Def.Generate (Def.cs file contains the ToyScript node representing function definition) by calling tg.PushNewScope(_name).

The CodeBlock then lets ToyScript compiler create local variables, add parameters and ultimately populate the body.

Now that we are looking at the Def.Generate, it is also quite straightforward how the second code block which represents the top level code came to being:

//
// AST D:\Dev\x.ts
//

.codeblock Object D:\Dev\x.ts ()() {
    .var Object f (Local,InParameterArray)
    {
        (.bound f) = (ToyFunction.Create)(
            "f",
            .new String[] = {
                "a",
            },
            .block (f #1
                Declarative
            ),
        );
        .action (Object) Call( // CallSimple
            (.bound f)
            "Hello ToyScript"
        );
    }
}

Similar to Python, where functions are first class and defining a function essentially means assigning the instance of the function object to a variable named after the function's name, making:

def foo(): pass

pretty much equivalent to:

foo = def (): pass

... if the following syntax were allowed, that is. Same for ToyScript. The Def.Generate() builds such assignment. It creates a variable named after the function name (or uses existing one if it already exists) and assigns a result of a call to ToyFunction.Create to it. The ToyFunction.Create, in addition to function name, accepts the names of arguments and the delegate to the function body (expressed as CodeBlockExpression).

The last node in the top level code is the actual function call. The exact semantic is: "Call the content of the variable 'f' with the argument 'Hello ToyScript'". Since what the call actually does depends on the contents of the variable "f", it is a dynamic operation expressed via an ActionExpression - a gateway into the dynamic behavior of the DLR.

In Dynamic languages the dynamic operations are everywhere - a reason why we were avoiding operators until now. For example, another trivial function:

def add(a, b) {
    return a + b
}

also uses ActionExpression to express the dynamic operation "Add":

.codeblock Object add ()(
    .arg Object a (Parameter,InParameterArray)
    .arg Object b (Parameter,InParameterArray)
) {

    {
        .return .action (Object) Do Add( // DoOperation Add
            (.bound a)
            (.bound b)
        );
    }
}

In short, the ActionExpressions represent dynamic operations whose exact semantic is not known at compile time and has to be determined at runtime. In the next chapter we'll explore how the runtime semantic can be captured using the ActionExpressions, and using other means as well and how the Dynamic Language Runtime works with respect to the dynamic operations expressed as Actions.