In this chapter we will closely examine the basic constructs from which programs are built in Slogan. Like any other powerful language, Slogan allows us to combine these basic elements to build complex ones. We will also have a sneak peek at some of the means of abstractions offered by Slogan.1
The preceding chapter showed how to use the REPL to interactively run code. The program fragments that you type in are called expressions. The Slogan interpreter responds by displaying the value of those expressions. One of the most primitive expression is a number. As there is nothing much to be done to a number in isolation, the interpreter will just give it back.
2017
// 2017
Numbers can be combined together using the familiar arithmetic operators to produce new values:
2017 + 1
// 2018
1000 - 300 + 3
// 703
1000 - 300 * 3
// 100
4/2
// 2
The arithmetic operators follow the commonly understood precedence and associativity rules. Multiplication and division takes precedence over addition and subtraction. The arithmetic operators with the same precedence associate from left to right. Parentheses are used to explicitly override precedence or group parts of an expression that should be evaluated first.
(1000 - 300) * 3
// 2100
There are many more operations you can perform on primitive data like numbers. These operations are all defined as functions. A function is usually invoked by calling its name followed by a list of arguments enclosed in parentheses. Examples of some useful numeric functions are given below:
In fact, the arithmetic operators are all functions with special syntactic support from the language. The functions associated with
the operators can be accessed by enclosing the operators in tick-quotes (`
).
In this section we have introduced two important concepts – expressions and values. A value is the result of
an expression. To repeat the point with a simple example, 10 * 2
is not a value because a computation can still be performed on it.
The result of that computation – the number 20
– is a value because no more operations can be performed on it.
If we can associate a name with a value, it will be easier to refer to that value later. The names given to values are called variables.
We name things with the let
statement.2
let rectangle_width = 23
let rectangle_length = 52
let rectangle_area = rectangle_width * rectangle_length
rectangle_area
// 1196
The let
statement is Slogan's simplest means of abstraction, for it allows to refer to results of
complex computations by name.
The names that we use for variables are generally known as identifiers. Keywords, variables, and symbols are collectively called identifiers. Identifiers may be formed from letters, digits and these special characters: _, $, %, and @. Some characters are reserved by the language for special purposes, mostly to be used as operators. The characters that cannot normally be used in identifiers are: +, -, /, *, <, ', >, =, \, #, ., (, ), {, }, [, ], ,, :, ; &, ? and |.
Identifiers are case-sensitive. For example, rectangle_width
and Rectangle_Width
are considered to
be different identifiers. There is no inherent limit to the length of identifiers.
A number of identifiers are reserved by Slogan for syntactic definition. These are known as reserved words or keywords. They cannot be used as names for variables. The reserved words in Slogan are listed below:
function
module
record
true
false
if
else
when
let
letfn
letrec
letdyn
yield
case
match
where
try
trycc
catch
finally
namespace
declare
assert
for
break
continue
It is possible to have multiple expressions evaluated in sequence and have a single value returned. Such groupings of expressions are
known as code blocks. The expressions in a code block are enclosed in opening and closing curly braces. ({ }
).
The expressions in a code block is evaluated one after the other in the order they appear and the value of the final expression
will be returned as the value of the entire block.
{ showln(1 + 2)
showln(2 * 2)
sqrt(5 + 2) }
//> 3
//> 4
// 2.6457513110645907
A code block is like any other expression that evaluates to a single value. It can appear wherever ordinary expressions are legal.
The scope of variables defined in a code block are limited to that code block. They won't be visible to code outside the block. All variable definition statements must appear at the top of the code block.
let x = 100
{ let x = 200
x + 10 }
// 210
x + 10
// 110
The previous section described how values can be abstracted away by assigning them to variables. Function definitions are a more powerful abstraction mechanism by which an expression itself can be referenced by a name.
Imagine how you would express the conversion of temperature from Celsius to Fahrenheit. The procedure can be described in English as "multiply temperature in Celsius by 9, then divide by 5, then add 32". This description can be translated to a Slogan function as
function celsius_to_fahrenheit(temperature)
temperature * 9 / 5 + 32
A function definition is introduced by the function
keyword. It is followed by the function's name.
The celsius_to_fahrenheit
function requires the caller to specify the temperature in Celsius that needs to be converted.
This value becomes an entry in the parenthesized parameter list of the function. The parameter list is followed by an expression
which is the formula for converting temperature
to Fahrenheit. Unlike functions in imperative languages such as C, no explicit
return
statement is required to return a value from the function. The value of the last expression is implicitly
returned as the value of the function call.
Here are a few instances of using the temperature conversion function:
celsius_to_fahrenheit(180)
// 356
celsius_to_fahrenheit(30)
// 86
Exercise 3.1. Define a function to express the idea of squaring, i.e, multiplying a number x
by itself.
Exercise 3.2. Write a function that takes the radius of a circle as argument and return its area. The area of a circle
is computed by the formula: area = π × radius2
.
Slogan represent truth or Boolean values with the
identifiers true
and false
. They are used as
values for operations that return an yes or no answer. For instance, we can check if two values are equal
and get back a Boolean result:
1 == 1
// true
2 == 3
// false
We can also do other kinds of comparisons like checking if a value is less than or greater than another value:
1 < 2
// true
1 < 1
// false
1 <= 1 // less-than-or-equal-to
// true
1 > 2
// false
1 > 1
// false
1 >= 1 // greater-than-or-equal-to
// true
1 <> 2 // not-equals
// true
not(1 == 2)
// true
// like arithmetic operators, comparison operators are also real functions
`<`(1, 2, 3, 4, 5)
// true
`<`(1, 2, 3, 4, 4)
// false
`<=`(1, 2, 3, 4, 4)
// true
Often we want to make a decision based on more than one Boolean value. The two logical operators - &&
(and
) and ||
(or
) - are used for
combining Boolean values.3
1 < 2 && 2 == 2
// true
and(1 < 2, 2 == 2, 3 > 2)
// true
1 < 0 || 2 == 3 || 3 == 3
// true
or(1 < 0, 2 == 3, 3 > 4)
// false
Conditional expressions allows us to make a test and perform different operations based on the result of that test.
The most general conditional is the if
expression.4
The syntax of if
is shown below:
if (condition_expression)
consequent_expression
else
alternative_expression
If condition_expression
evaluates to true
, consequent_expression
is
evaluated, otherwise alternative_expression
is evaluated. The value of the expression that gets evaluated will
become the value of the whole if
expression.
Let us write a function that makes use of comparison operators and condition expressions. The function is called reciprocal
and it computes 1/n
for a number n
when it is not equal to 0
.
If n
is 0
reciprocal
will return the string "oops!"
.5
function reciprocal(n)
if (n == 0) "oops!"
else 1/n
Examples of using the reciprocal
function are shown below:
reciprocal(10)
// 1/10
reciprocal(1/10)
// 10
reciprocal(0)
// oops!
reciprocal(reciprocal(1/10))
// 1/10
Alternative_expression
can be another if
expression, making it
possible to check for multiple conditions. This is demonstrated by the next function which calculates train fare discounts based on the age
and sex of the traveler. The rules for computing the discounts are these – if the traveler is female, give a 7%
discount,
if the traveler is more than 60
years of age, add 12.5%
to the discount. The function takes
the original fare, the age of the traveler and a Boolean value that indicates whether the traveler is female or not.
function discount(fare, age, is_female)
if (is_female && age > 60) fare * (0.07 + 0.125)
else if (is_female) fare * 0.07
else if (age > 60) fare * 0.125
else 0
// Usage:
discount(1230, 61, true)
// 239.85
discount(1230, 42, true)
// 86.10000000000001
discount(1230, 42, false)
// 0
discount(1230, 63, false)
// 153.75
Alternative_expression
is optional. If it is omitted and consequent_expression
evaluates to
false
, if
will return false
.
if (1 < 2) 100
// 100
if (1 > 2) 100
// false
The when
expression is a cleaner alternative to if
expressions
without the else
clause.
when (1 < 2) 100
// 100
when (1 > 2) 100
// false
Though Slogan support two explicit identifiers to represent Boolean values, all values other than the identifier
false
is treated as true by the conditional expressions.
if (1 + 2) "ok" else "no"
// ok
Exercise 3.3. Define a function that takes three numbers as arguments and returns the sum of the squares of the two larger numbers.
Some problems require iterating over the same expressions for a given number of times. Most imperative languages provide
constructs like the for
and while
loops for this purpose. Slogan gives you a simpler solution –
if a function wants to repeatedly do some stuff, just make recursive calls to itself. If the recursive call is made at a
position which is the last expression in the function, Slogan will
arrange its internal affairs in such a way that the call stack won't overflow.
The next example defines a function that prints a table of Celsius to Fahrenheit conversions. It take three arguments - the lower limit of the table, the upper bound and the size by which each entry in the table differs.
function print_temperature_table(lower, upper, size)
when (lower <= upper)
{ showln(lower, "c:", celsius_to_fahrenheit(lower), "f")
print_temperature_table(lower + size, upper, size) }
This function makes use of the celsius_to_fahrenheit
function we defined earlier.
Print_temperature_table
will call itself until the limit is reached. This recursive call is made as the
last expression in the conditional block. This also happens to be the last expression executed by the function. So Slogan translates
this into an iterative call, removing the previous call stacks from the call frame.
This optimization allows the function to call itself as many times as it wants and no special looping construct
is needed.6
Before we move to the next topic, let us once see the print_temperature_table
in action:
print_temperature_table(0, 100, 20)
//> 0c:32f
20c:68f
40c:104f
60c:140f
80c:176f
100c:212f
Exercise 3.4. Read the Wikipedia article on ancient Egyptian multiplication. Use recursion to implement one of the algorithms described there.
Now we know enough about Slogan to fix the first problem that our time server had – its inability to handle more than a single client. This can be fixed using recursion. We will put the basic behavior of the server in a function that calls itself for handling each client.
// file: time_server02.sn
let server = tcp_server_stream(2121)
function client_handler()
{ let client = read(server)
let request = read_line(client)
if (request == "GET TIME")
showln(stream = client, time_to_string(now()))
else
showln(stream = client, "error: invalid request")
close_stream(client)
// call self to handle next client
client_handler() }
// start the handler
client_handler()
// we will never reach here
close_stream(server)
To try the new server, load the time_server02.sn
script into the REPL:
load("time_server02")
Start another REPL and load the client time_client.sn
script.
To send multiple requests to the
server, just reload the client multiple times. The new server will be able to handle these requests without requiring
a restart. A significant improvement indeed!
load("time_client")
//> 2017-04-30T08:00:48
load("time_client")
//> 2017-04-30T08:00:49
As the server always expects another client to connect, it will never terminate. You can terminate the server process by pressing the
Control+C
key-combination in the shell that the server is running.
1Starting from this chapter, we will only show code snippets and their output without
the slogan>
prompt. You may evaluate the programs interactively at the REPL or type them into a file that is compiled
and executed later. While using the REPL, remember to end all expressions with a semicolon (;
), so that the interpreter can
kick-in and evaluate that expression. Semicolons are normally not required to terminate expressions entered into a file. So we omit them
in the code examples as well.
2A statement is often defined as code that does something and does not produce
a value. Slogan do not have statements in this sense, it only have expressions. Even statements like let
return a value. This value is a special object known as void
. So, in the context of Slogan, a statement
is an expression that returns void
.
3&&
takes precedence over ||
.
4If
is an expression because it returns the
value of the expression that was conditionally evaluated. This may come as a surprise for some among the readers as most popular languages
treat conditionals as statements.
5The string data type will be covered in detail in the next chapter.
6Slogan do have a built-in imperative looping construct which is similar to the
for
loop in C. But Slogan's for
loop is more powerful and expressive, as you will discover later. It is also
possible to add new looping constructs to the language using the syntactic extension facility. Despite all this, the preferred way
to repeatedly execute a piece of code in Slogan is by tail-recursion!