Programming paradigms

The program consists of a sequence of statements which update the state of variables. Usually close to the underlying hardware.

Declarative

The program specifies what needs to be done and let the language decide how it is done.

Functional

Programs consist of functions that compute values based on input.

Lazy execution

Expressions are not evaluated until they are needed.

Concurrent

Programs are able to do several operations at the same time.

Logic programming

A program is a set of logical assertions and can be used to find out if a query is true.

Declarative programming

A program can be considered declarative if it is/have:

immutable state

independent of any external state

deterministic

In other words, every time you run a declarative program with the same input (or just a declarative component) it will give you the same result (i.e. output).

Observal declarativity

A component can be seen as declarative if the definition is, even though the implementation is not

Example: A database with a declarative interface.

Higher-order programming

In higher-order programming you pass procedures/functions/objects as parameters or return values. You can also embedd them in datastuctures.
(E.g. pass procedures as arguments to other procedures)

Closure = procedure value.

Four basic operations underlie all techniques in higher order programming

Procedural abstraction

Any statement can be put into a procedure.
This simple snippet of code can easily be rewritten to a general procedure that would multiply the variable passed to it by two.

local B = 21 Result in
Result = B * 2
end

And here the equivialent procedure:

proc {MultiplyByTwo B ?R}
R = B * 2
end

Genericity

A generic function lets any specific entity(i.e. any operation or value) in the function body become an argument of the function. Given the above multiplying example, we can make the procedure generic(here, we also use a function, not a prodedure):

In ApplyFunction, we don't have any values or operations inside the function body. Instead, we extensively use the formal parameters to do our computation. We have to make the function that will act on two inputs, in this example the Multiply function. The constant $ 2 $ is also not seen in ApplyFunction's procedure body, but rather passed as an argument. It is worth noting that any other function can be specified, so ApplyFunction can have some really interesting behaviours.

Instantiation

The abillity to return procedure values as results from a procedure call.

The act of passing procedure values to a generic function which again returns a more specific procedure which takes less/other parameters.
Given a general sorting function Sort that requires the unsorted list and a comparison function, we can instantiate this general function to sort various entities.

We can imagine sorting everything from numbers and letters to raccoons with this function. Calling MakeSort instantiates the specification. It returns one sorting rutine out of several possible ones, this is called the instance of the specification.

Embedding

Procedure calues can be put in data structures. An example could be the following:

Relational Programming

Relational programming is an approach to programming where procedures
are thought of as relations between values, and where no clear distinction
between input and output is made.

The kernel language is extended with two statements:

$$ choice\ \langle s \rangle_1\ []\ \dots\ []\ \langle s \rangle_n\ end $$
and $ fail $. These statements allow us to search for alternatives, fail, and try another alternative if it is wrong. choice statements are executed in the order they are encountered. Using this, one can state a problem, and use Oz to generate the answer.

Formal grammar

A formal grammar consists of:

A set of terminals, $S$

A set of non-terminals, $V$

A set of rules, $R$

A start symbol, $v_s$, $v_s \in V$

A grammar $G$ is defined as a tuple, $G = (V, S, R, v_s)$

EBNF

Extended Backus-Naur Form is a metalanguage. It is used to describe the grammar of other languages. The rules of grammar of a language can be expressed like this:

$$ \langle c \rangle ::= a \langle c \rangle a |\epsilon $$

Explanation: The lefthand-side is a non-ternimal (variable) which has two production rules. $ \langle c \rangle $ can become $a \langle c \rangle a$ or $\epsilon $ (the empty symbol). $a$ is a terminal (symbol).

EBNF consists of terminals and variables. We can use V for variables and S for symbols.

Chomsky's hierarchy of languages

Unconstrained languages

All grammar is of the form
$$ a ::= b $$
where $a,b \in (V \cup S)$.

Context-sensitive languages

All rules are of the form
$$ avb ::= ayb $$
where $v \in V$ and $a,b,y \in (V \cup S)$.

The rule only applies if the variable $v$ is surrounded by $a$ and $b$. Hence context-sensitive.

Context-free languages

Generated by context-free grammar. This means that every production rule is of the form
$$ A \rightarrow a' $$
where A is a variable and a' a sequence of variables and terminals.

Synatax/parse trees

Each derivation gives rise to a parse tree. The parse tree is then linked to how the sentence was built from the start symbol. More often than not there are many ways to arrive at a sentence. Given a parse tree one can read the leaf nodes(terminals) and understand the sentence.

A parse tree can be used to; interpret a program(in interpreted languages), generate code(in compiled languages) or optimize intermediate code(both comiled and interperted).

Ambiguity

A grammar is abiguous if a sentence can be parsed in more than one way. This means that the sentence has more than one parse tree. Programming languages cannot be ambiguous since that would give rise to several different meanings of the same sentence.

There are several ways to avoid ambiguity in grammars: Parantheses, precedence and associativity.

Scope

The scope of a name binding is where in the program the name binding is valid. (E.g where it can be used). A name binding is a association between a identifier and variable/value.

Lexical scope

The following example demonstrates the difference between lexical(static) and dynamic scope. What is the resulting output of the program?

local P Q in
proc {Q X} {Browse static(X)} end
proc {P X} {Q X} end
local Q in
proc {Q X} {Browse dynamic(X)} end
{P hello}
end
end

Here, we have created the identifier 'Q' twice. It refers to different prodedures, browsing either static(hello) or dynamic(hello). In static scoping, we can find the value of an identifier simply by looking at its scope. This results in printing 'static(X)' in the program above.

Dynamic scope

Dynamic scoping keeps the scope of the caller when calling a function. Thus, the program above would result in printing 'dynamic(X)'. This is because it created a new identifier 'Q', bound this to a procedure, and then called the procedure P.

Syntax, Semantics and DSKL

Syntax & Semantics

The syntax is a set of rules, principles and processes that govern the structure of sentences in a language.
There are three levels of syntax.

We regard words on the lexical level, how characters form tokens. The lexical syntax can be expressed in a formal regular grammar. We use regular expressions(from regular grammar) to define lexemes.

The grammar level determines how tokens form phrases. Phrases are expressed in a context-free grammar.

The also exists the context level that determines what objects and variables refer to.

The semantics gives rise to the meaning of programming languages.

Syntactic Sugar

Syntactic sugar are abbreviations of longer and often less readable code. It does not introduce any new functionality. In oz we can declare variables in a number of ways;

local X in
local Y in
# Code
end
end

Or we can do the cleaner

local X Y in
# Code
end

Where the latter is syntactic sugar for the former.

Linguistic abstractions

Linguistic abstractions are another way to express functionality that is already in the language. A great example of this is the following:

if <expression> then
# Code
end

is actually the following code in disguise

if <expression> then
# Code
else
skip
end

Here we seem to have introduced a new kind of if-statement, namely the if-statement without an 'else' clause. However, this is not the case, as the former code is really the latter.

Abstract Kernel Language Machine

The abstract machine(or virtual machine(VM)) is a model of computation, explaining the execution of programs in a high level way. We can easily expand the VM to support several programming constructs(?) such as error handling and parallelism.

Components of the VM

The VM consists of two parts: The Semantic stack(SS) and a single assigment store(SAS).

The stack contains semantic statements. Each semantic statement is a pair of a parsed statement and an environment. The statement is a small piece of code to be evaluated. The environment is a mapping from identifiers in the code to variables in the store.

Execution on the VM

The program is executed on the VM. This execution is roughly done by looking at the topmost statement in the SS, then manipulating the SAS accordingly, followed by the next statement in the SS. Here is an example where we want to unify two unbound variables follow:
Before the execution we have:

Single Assignment Store(SAS)

The sas consists only of declarative variables, that is variables that stays bound throughout the computation, and is indistinguishable from their value. This means that $ X * Y $ is the same as $ 2 * 5 $ if the store is $\{ X \rightarrow 2, Y \rightarrow 5 \}$.

Variables

When writing $ X $ in oz, X is the variable identifier of a variable in the store($ \{ X \rightarrow \nu_1 \}) $ and \nu_1 is the actual variable.

Dataflow Variables

In our declarative programming model, creating a variable and binding it are done separately(this is why we can have unbound variables). Declarative variables that cause the program to wait until they are bound are dataflow variables. This is useful when doing concurrent programming(which we will get back to later).

Tail Recursion

When a function calls itself(or another function for that matter) at the end of the function, it is called tail recursion.
Whenever a program calls itself, a lot of data are being pushed on the stack(program counter, environment, return address). The stack might fill up if the recursion goes to deep, resulting in a program crash. Tail recursion avoids this problem:

fun {Member Xs Y}
case Xs of nil then
false
[] Head|Tail then
if Head == Y then
true
else
{Member Tail Y}
end
end
end

In the program above we see that the last thing our function does is to call itself. The other variables(Head and Tail in this example) will never be needed again after the recursive call to Position. Hence the abstract machine does not need to push the environment on the stack. Indeed, does not need the stack at all because the program might as well be done iteratively. We can also express this in a formal grammar:

$$ p \rightarrow 1p $$$$ p \rightarrow 1 $$

Since the last $ p $ in rule one is at the end, it is tail recursive(nothing comes after it), and till the next recursion we can forget what was before it. Turning the above grammar into iteration:($ + $ means one or more)

$$ p \rightarrow 1^+ $$

Exceptions

To implement exceptions, we expand extend the kernel language both semantically and syntactically.

The try statement contains two statements, namely one in try and one in catch. The two statements are pushed on the stack, firstly the statement inside catch, then the statemenet inside try. If something fails in the try statement, the VM can just pop everything of the stack until it reaches a catch, which will resolve the problem.

The raise statement will pop a semantic statement from the stack, if the statement is a catch statement, push that semantic statement onto the stack. If it isn't a catch statement, repeat.

Oz

Functions and procedures

Functions returns a value, procedures do not. That means that procedures do not care about the number of input, nor the number of output values.

Procedures are a basic type of Oz, which functions are based upon.

proc {NameOfProcedure V1 ?V2} % Declare a procedure
% Do something
V2 = v1
end
% The question mark in front of the output argument V2 is just to make the definition easier to read, and has no effect on execution
fun {NameOfFunction V1 V2}
% Do something with V2 or whatever you want, I don't really care cause' this is an example
V1 % function returns V1
end
% Call them
local
X = 2
Y = 1
Z
in
{NameOfProcedure X Z}
{Browse Z} % returns 2, as the procedure binds V2 to the value of V1
Z = {NameOfFunction Y X}
{Browse Z} % returns 1, as the function returns V1 which is Y
end

The function statement is syntactic sugar. Let's translate a function to a procedure (page. 84):

We see that the function lets you return a value, which is the same as giving a procedure an argument and binding it to the return expression.

On the semantic stack, a procedure is stored as a procedure value, $ (d_c, E_c) $ . The procedure value is a touple. The touple consists of the procedure definition $ d_c $, which contains the statements (the procedure body) in the procedure. It also consists of $ E_c $, which is the procedure's environment. Note that $ E_c $ must contain, in addition to the formal parameters of the procedure and all local variables, the free identifiers in the procedure body.

Higher order functions

Dataflow

Declarative variables that cause the program to wait until they are bound are called dataflow variables.
In the declarative model, creating a variable and binding it are done separately.

Records

Records are a basic type of Oz. They are of the form

X = person(name:"George" age:25)

much like dictionaries in Python (associated list), and contains a tuple of literals.
Operations:

Touples

A touple is a data type that contains several literals. They are of the form

(name:"George" age:25)

Literals

A literal is an atom or a name. This could be seen as

25

in the touple above.

Lists

List are nested records.

% Equivalent:
List1 = '|'(1 '|'(2 '|'(3 nil)))
List2 = [1 2 3]

Difference lists

A difference list is a pair of two ordinary lists, where each might have an unbound tail. It must be possible to get the second list from the first list by removing zero or more elements from the front of the first list. Example: