This is an old revision of the document!
Functional and Imperative Programming
This part of the tutorial shows AmbientTalk as a simple expression language with a minimum syntax which resembles very on Java script. This section mainly describes the basic features of the language, namely variables, functions and tables and control flow.
Variables
As usual, one can define, assign and refer to a variable. Variable definitions are made with the keyword def. Note that AmbientTalk is a dynamically typed language so, variables do not have a type but, they just contain values.
In the examples we use the interactive AmbientTalk shell (iat) where the input and output prompt are represented by > and » , respectively.
>def x := 5 >>5 >def y := x + 2 >>7
Variable definitions can be combined with assignments as shown above. As in Pico, assignments uses the “:=” operator. Note that there must be an space between the variable and the “:=” operator in order for the parse to resolve the ambiguity between a keyword message and a assignment, e.g. “a := 1” is understood as an assignment while “a:” as a keyword. We will further elaborate on keywords in the following sections.
An assignment consists of one or more expressions, providing that the number of expressions on the right hand side match the number of variables on the left hand side. This allows a permutation of variables such as:
>[x, y] := [ y, x ] >>[7,5]
Reference is just done by evaluating the variable.
Tables
Indexed tables represent what other languages call arrays or lists. Tables are unidimensional and their indexes range from 1 to the size of the table. As variables, one can define, assign and refer to a table. Table definition is also made with the keyword def in the following form:
def t[ <size> ] { <expression> }
This means that the <expression> will be evaluated <size> times, i.e., one for each slot of the table. This allows expressions such as initializing a table of ascending numbers as shown below:
>def z := 0 >>0 >def table[5] { z := z + 1 } >>[1, 2, 3, 4, 5]
Although there is no special constructor for definition of multidimensional tables, a table entry can contain another table. This is internally stored as a unidimensional table whose entries are other tables.
>def vocals := ["a", "e", "i", "o", "u"] >>["a", "e", "i", "o", "u"] >table[3] := vocals >>[1, 2, ["a", "e", "i", "o", "u"], 4, 5] >table[3][2] >>"e"
As shown in the definition of the varible vocals, evaluating a series of comma-separated abstract grammar values between square brackets (aka a tabulation) results in a table.
>[ 1, table, "ambientTalk"] >>[1, [1, 2, ["a", "e", "i", "o", "u"], 4, 5], "ambientTalk"]
Table Splicing
TODO!
Functions
As variables and tables, functions are defined with the keyword def in the form of:
def functionname( <arglist> ) { <body> }
The argument list is just a list of local variables which are always evaluated one by one from left to right. A basic function looks like this:
>def square (x) { x*x } >>nil >square(5) >>25
This example also illustrates how functions are called. Calls to functions without parameters must also include the parenthesis as shown below.
>def f(){nil} >><closure:f> >f() >>nil
The return value of a function is the result of the last statement executed. Functions must always return a value - i.e. they cannot be abstract. The example also illustrates how to create dumb function that doesn't do anything but returning the nil object.
Functions have access to the enclosing environment of its definition as shown in the following example.
>def counter := 0 >>0 > def inc() { counter := counter + 1} >><closure:inc> >inc() >>1
Functions can call themselves recusively and they can also be nested in the definitions of other functions such as:
>def fac(n) { def inner(n, result) { if: (n =0) then: { result } else: { inner( n-1, n * result) } }; inner(n,1) } >><closure:fac> >fac(5) >>120
Variables and functions defined locally to functions are only visible in the scope of the function where there were defined. In the previous example, fac uses a local function inner that is only visible inside fac and its nested scopes, in the example fac.inner.
Variable-Length Argument Functions
You can create functions that take an arbitrary number of arguments by means of the splicing operator @ as shown below:
>def sum(@args){ { def total := 0; foreach: { |el| total := total + el } in: args; total} >><closure:sum> >sum(1,2,3) >>6
When the sum function is called, the arguments are passed to the function in a table called args which can also be modified inside the body of the function. An alternative definition of the sum function follows:
>def sum(a, b, @rest){ { def total := a + b; foreach: { |el| total := total + el } in: rest; total} >><closure:sum> >sum(1,2,3) >>6
In this example the sum function accepts an arbitrary number of arguments as long as two arguments, a and b, are supplied. a and b are thus considered as mandatory arguments. A function can also declare optional arguments as shown below:
>def incr( number, step := 1){ number + step} >><closure:incr> >incr(3) >>4 >incr(3,3) >>6
Closures
As you have probably noticed in the previous examples, the value returned by a function definition is a closure. Actually in AmbientTalk functions are implemented as named closures.
The function name can be thus used to refer the function (without calling it). This will also return a closure to that function.
Blocks
In AmbientTalk, blocks are merely syntactic sugar for anonymous closures (aka lambdas). Blocks are creating using the {} braces in the form of:
{ |<parlist>| <body> }
If the block do not require any parameter, the |<parlist>| can be omitted. Consider a basic block to sum two numbers:
>{| a, b| a+ b} (3,2) >>5
Note that the argument list passed to the block can define the different types of arguments previously explained.
>{|a, b, @rest| def total := a + b; foreach: { |el| total := total + el} in: rest; total }(1,2,3) >>6
This example also illustrates that blocks are also used to iterate over enumerations, such as in foreach: {} in: table.
AmbientTalk doesn’t support function assigment. However, one can assign blocks to variables. In order to call the block the name of the variable must be used. If the block defined parameters, these are required to the call as argument list. What follows is an example of such manipulation:
>def square := { |x| x * x } >><closure:lambda> >square(1,2) >>3