Quick tour

--== OBSOLETE ==--

Introduction

 

Before we begin, let us mention that symbols, a.k.a identifiers, can use the whole character set. Therefore names like 'this-is-my-symbol' are perfectly valid symbols. Now, let's go on with the show!

 

 

Flexible typing

 

Types are much more flexible than in usual languages. You can specify what type a variable or even an alternative among several types! There is also the '?' meaning it can be of any type.

 

declare

name :: String

my-pet :: Dog | Cat | Mouse

item :: ?

 

data Dog

race :: Race

age :: Int

...

...

 

 

Notice that types are not enums but true data type constraints. To define a type, the keyword 'type' is used.

 

type Pet = Dog | Cat | Mouse

 

declare

my-pet-bis :: Pet

 

In this case it is used like a handy shortcut. But types can define anything, from data to functions or even agents! They can also be parametrized. This is for example handy in data structures or higher order functions.

 

data Tree(x)

Node :: x

children :: List(Tree(x))

 

 

type Tree-Of-Strings      = Tree(String)

type Tree-Of-Many-Ints = Tree(List(Int))

 

type Mapping(a,b) = function(a) -> (b)

 

Using type inference, it is possible to verify at compile-time the corectness of the type constraints.

 

 

High-order functions

 

Functions can be passed as arguments, returned and handled like any other value. Below is an illustration of the usefulness of being able to pass a function as parameter.

 

function sort(list, comparator) -> sorted-list

# ...

# apply your favourite algo here

# ...

 

 

my-list = [(1,2), (5,7), (4,3), (3,2)]

 

my-sorted-list = sort (my-list, function (a, b) -> (a.1 + a.2 < b.1 + b.2))

 

But returning new functions can be useful as well. A practical example would be computing a derivative. It takes a function as parameter and computes (an approximation of) the derivative function out of it.

 

function derive (f) -> (df)

df = function (x) -> (f(x + 0.001) - f(x)) / 0.001

 

my-f = function (x) -> x ^ 2 + 1

 

my-df = derive(my-f)

 

print my-df(0)   # prints 0.001   (due to the approximation error)

print my-df(4)   # prints 8.001   (approx error again)

 

It might not be the best example in the world but it shows the use of high-order functions. You will see high-order functions can be very handy in many situations.

 

Another important aspect of functions is that they must be stateless. This implies useful properties. Functions applied with the same arguments will always return the same result, whatever the state of the system is. Moreover, they are also more tractable since they do not produce side effects. Lastly, it opens opportunities for automated parallelization.

 

 

Message passing

 

Functions and data are perfect for any kind of computation. However, they are not really suited to interact with the environment. This is where agents come in play. They are self-contained entities using message passing. Below is a small example of a message exchanges. Upon reception of the message 'Clic' from 'my-button', it will display a message on the screen.

 

<< my-button : Clic

>> screen : Message-Box "You have clicked on the button!"

 

Nothing really new here. Simple message passing. However, you don't have to add listeners for instance, this is done implicitly. You have a direct model-view-controller where event raisers and listeners are automatically connected.

 

Moreover, this can apply both to local and remote objects. Indeed there is no conceptual difference between local and remote messages. The only difference is the path travelled by the message. This enables us to treat local and remote messages the same way:

 

>> some-web-server : "Hello server!"

<< ...

 

the above line sends the string "Hello server!" to the given web server. Using what protocol you may ask? ...That's up to you. The protocol is determined by the agent type of 'some-web-server'.

More on agents will be seen later.

 

 

Syntax extensions

 

TODO

 

 

Lazy evaluation

 

This may be misleading for programmers too accustomed to imperative paradigms. In Arplan, '=' is seen as a definition, an immutable true equality. Hence, it does not matter in what order the definitions are written nor evaluated since the result will always be the same. The following example shows a perfectly valid code sample.

 

z = x + y

x = 2

y = 3

 

Since definitions are only evaluated when needed, even more suprizing things can be done with this. For instance:

 

function roots(a,b,c) -> ans

delta = b ^ 2 - 4 * a * c

r1 = (- b + sqrt(delta)) / (2 * a)

r2 = (  b + sqrt(delta)) / (2 * a)

 

if a == 0

if b == 0

ans = error!

else

ans = (b / c)

if delta < 0

ans = ()   #an empty tuple, nothing

else

ans = (r1, r2)

 

The example above shows many interesting things. First, a function can have multiple output types at the same time. Then, r1 and r2 do not produce errors since they are not evaluated at all when 'a' is zero.

In other words, only what is really needed in the end is computed.

 

 

Built-in set manipulations

 

There are powerful built-in data structures: tuples, lists and maps/sets. Below is an example of how sets might be used.

 

numbers = {1, 2, 3, 4, 5, 123, 4444, 56456567776889793234235345}
even-numbers = {n | n in numbers and n mod 2 == 0}
odd-numbers = numbers -- even-numbers

 

The set 'odd-numbers' is thus the substraction of two sets. All other set operations are supported and as a result it is equally powerful and expressive than SQL. For example:

 

rich-guys-from-USA = {e.name |

e in employees
p in persons

e.id == p.id


e.salary > 5000

p.country == USA
}

 

In fact, you can even do more than in SQL since it is seamlessly integrated in the language.

 

 

Implicit parallelism

 

Parallelism is not only automatically included in functions but also in other common operations.

 

for e in employees

e.salary := 1.1 * e.salary

 

In the above example, there is no order in which employees salary is updated, it is done parralelly. To illustrate this better, let us consider the following example.

 

tuple := [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]

for i in {2...10}

tuple.(i) := tuple.(i-1)

 

In classical imperative languages, we would obtain a tuple full of 10. Since it is run in parralel here, we obtain:

 

print tuple # prints [10, 10, 20, 30, 40, 50, 60, 70, 80, 90]

 

Indeed, each entry in the tuple is now equal to the entry in the previous position.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

--------------------------------------------------------------------------------------

 

WARNING:

 

Up to this point, what follows is obsolete. The information it contains may be misleading or even plainly wrong and has to be rewritten.

 

Moreover, the formatting destroyed indentation in code samples making them unreadable.

 

 

Agents, conversations & synchronization

 

An agent is an independently running entity. That means, several agents can compute things in parallel at the same time. They communicate among each other using message passing, a bit like mini-servers.

 

Features:

- listen to everyone or a specific source

- send to everyone or a specific source

- the notion of conversation

- no race conditions / queuing

 

agent ServerInteraction

console = System.console;

 

run

>> console : "Please enter server address (IP:port) :"

<< console : ip:port

server := run Socket(ip:port)

 

request-polling

>> console : "Enter the message you want to send:"

<< console : "exit"

stop self

<< console : request

 

>>  server : request

<< server : response

 

>> console : "Response:\n" ++ response

request-polling

 

 

 

stop

stop console

 

 

Aspect oriented agents

 

What is achieved in aspect oriented programming can also be done using plain normal agents. As a reminder, the goal is the separation of concerns by extracting "boilerplate" functionalities. Imagine you would want to log each time an agent receives a message. Adding a few lines in each agent is a bit cumbersome. The idea is to make an agent that intercepts all messages received from anyone and logs them. This can be done easely as illustrates the following example:

 

 

agent Logging

rec << from : msg

self >> System.console : "Message received by " ++ rec ++ " from " ++ from ++ ": " ++ msg

 

 

 

Functions and actions

 

Note: actions may perhaps be completely removed from the language as they are partly redundant with agents.

 

To take things clearly appart, Arplan has two opposite constructs:
- functions which output values and have no side effects
- actions which return nothing and alterate the environment

 

action resetGame (difficulty)

lives := 3

score := 0
maze := buildRandomMaze(difficulty)

 

function buildRandomMaze (difficulty) -> (maze)

...

# this function may not modify outside variables

 

Indeed, creating a new maze since it doesn't need to alter the environment. Using functions, we are sure it doesn't.

 


 

 

 

 

 

 

 

The "lego" triangle

 

As you saw, there are 3 important constructs:

  • data
  • functions
  • agents

 

Each of these can contain inner declarations of any other type. Variables can contain any of these, messages can be functions, functions can return agents, actions can alter behaviors... basically everything is possible. The different constructs can be combined like lego blocks and provide very rich ways to interact together.

 

 

Pattern matching

 

As usual a small example is better than a long talk.

 

 

data Tree(x)

content :: x

children :: [] of Tree(x)

 

function describe(tree) -> descr

case tree of

Tree(a, [ Tree(b,[]), Tree(c,[]) ] )

descr = "This tree has exactly 2 leaves: " ++ b ++ " and " ++ c

 

Tree(a :: Int, [])

descr = "This is a leaf containg an integer: " ++ a

 

Tree(a :: String, [])

descr = "This is a leaf containg a string: " ++ a

 

?

descr = "This is some other tree..."

 

The variables in the pattern are bound to the corresponding content. On the first succesful match, the body is applied. In the pattern, you can find pure values, variables to be bound/matched, or the wildcard '?' which matches anything.

 

 

Meta-programming

 

The concept is that the program is a data structure itself which can be used like any other data structure. A program is just a list of statements after all. And each of these statements can be reduced to smaller chunks. Like in Scheme/Lisp this leads to the ability to transform the code by itself to make powerful abstractions, making it possible to define one's own additional constructs.

 

 

Data

This represents pure data. Unlike OO, they are independent of functions.

 

Functions

These are functions in the mathematical sense. That means, they cannot have side effects and cannot interact with the environement. It is purely for computation.

 

Agents

These are independently running stateful entities communicating by message passing. They include advanced concepts of conversation and synchronization.

 

 

 

 

 

my-list = [5, "dogs", "and", 3, "cats"]

 

case my-list of

[]

descr = "There are no animals!"

 

[q :: Int, a :: String]

descr = q ++ a ++ " are playing around"

 

[q1, a1, "and", q2, a2]

descr = "The " ++ q1 ++ a1 ++ " are chasing the " ++ q2 ++ a2 ++ "!"

 

?

descr = "Unexpected list content!"