.TLAddendum to.I "The Limbo Programming Language".AUVita Nuova.br30 March 2005.NH 1Introduction.LPThis addendum provides a brief summary of several language changes toLimbo since.I "The Limbo Programming Language"was last revised:.RS.IP •buffered channels.IP •unrestricted \f5alt\f1.IP •function references.IP •exceptions.IP •exponentiation.IP •fixed-point types.RE.NH 1Buffered channels.LPA buffered channel can now be declared:.P1c := chan[1] of int;.P2Here the buffer size is 1. A send on this channel will succeed immediatelyif there is a receiver waiting or if the buffer is empty. A receive on thischannel will succeed immediately if there is a data item in the buffer. This allows us towrite a very simple locking mechanism:.P1acquire(c: chan of int){ c <-= 0;} release(c: chan of int){ <-c;} new(): chan of int{ return chan[1] of int;}.P2The declaration.P1c := chan[0] of int;.P2is equivalent to .P1 c := chan of int;.P2An attempt to create a channel with a negative buffer size will raisean exception. An attempt to create a channel with a very large buffermay result in an immediate memory exception if there is not enoughroom for the buffer..NH 1Unrestricted.B alt.LPThe implementation has changed to remove the restriction that only one process can bewaiting in an.CW altto send or receive on a particular channel.The busy exception never occurs now. Thus you can do.P1i(){ c := chan of int; c1 := chan of int; spawn p(c, c1); <-c; spawn p(c, c1); <-c; for(i := 0; i < 20; i++) c1 <-= i;} p(c: chan of int, c1: chan of int){ c <-= 0; for(;;) alt{ i := <-c1 => ; }}.P2The two spawned processes can both wait on.CW c1without fuss.Processes are queued on a strict FIFO basis so, inthe example above, the two processes receive on.CW c1alternately..NH 1Function references.LPFunction references may be declared as follows:.P1fp: ref fn(s1: string, s2: string): int;.P2Given the function.P1cmp(s1: string, s2: string): int{ if(s1 < s2) return -1; if(s1 > s2) return 1; return 0;}.P2a reference to it can be created by assignment:.P1fp = cmp;.P2where the name can be qualified by an explicit module reference as usual:.P1fp = mod->cmp;.P2or it can be returned from a function:.P1Cmp: type ref fn(s1: string, s2: string): int; rcmp(s1: string, s2: string): int{ return -cmp(s1, s2);} choose(i: int): Cmp{ if(i) return rcmp; return cmp;}.P2(the declaration of the synonym.CW Cmpwas done only for clarity).They may be declared and passed as parameters:.P1sort(a: array of string, f: ref fn(s1, s2: string): int): array of string{ # ...} # ...b := sort(a, cmp);c := sort(a, rcmp);.P2The function is called via the reference by.P1 r := fp("fred", "bloggs");.P2Otherwise function references behave just like any other reference type..NH 1Exceptions.LPBoth string exceptions and user defined exceptions are now provided.The.CW Sysmodule interface to exceptionshas been removed and replaced by new language constructs in limbo..NH 2String exceptions.LPSimple string exceptions can be raised as follows.P1raise \fIs\fP;.P2where.I sis any value of type string (it need not be constant)..LPException handlers may be attached to a block (or sequence of statements) :-.P1{ foo(); bar();} exception e {"a" or "b" => sys->print("caught %s\en", e); raise;"ab*" => sys->print("caught %s\en", e); exit;"abcd*" => sys->print("caught %s\en", e); raise e;"a*" => sys->print("caught %s\en", e); raise "c";"*" => sys->print("caught %s\en", e);}LL:.P2.LPAny exception occurring within the block (and in nested function calls within the block) canpotentially be caught by the exception handler. An exception is caught by a guard exactlymaching the exception string or by a guard\f5\&"\fP\fIs\fP\f5*"\fPwhere.I sis a prefix of the exception string.The most specific match is used. Thus a raise of "a" will be caught by the firstguard and not by the fourth guard. A raise of "abcde" is caught by the third and not the secondor fourth. If a match is found, the sequence of statements following the guard are executed.If not, the system searches for a handler at a higher level..LPAs shown above, the exception is available through the exception identifier (e in this case) if given following the exception keyword..LPThe exception is reraised using.P1raise;.P2or.P1raise e;.P2.LPBoth the block and the exception code will fall through to the statement labelledLL unless, of course, they do an explicit exit, return or raise first..NH 2User-defined exceptions.LPYou can declare your own exceptions:.P1implement Fibonacci; include "sys.m";include "draw.m"; Fibonacci: module{ init: fn(nil: ref Draw->Context, argv: list of string);};.P3 init(nil: ref Draw->Context, nil: list of string){ sys := load Sys Sys->PATH; for(i := 0; ; i++){ f := fibonacci(i); if(f < 0) break; sys->print("F(%d) = %d\en", i, f); }}.P3 FIB: exception(int, int);.P3 fibonacci(n: int): int{ { fib(1, n, 1, 1); }exception e{ FIB => (x, nil) := e; return x; "*" => sys->print("unexpected string exception %s raised\en", e); * => sys->print("unexpected exception raised\en"); } return 0;}.P3 fib(n: int, m: int, x: int, y: int) raises (FIB){ if(n >= m) raise FIB(x, y); { fib(n+1, m, x, y); }exception e{ FIB => (x, y) = e; x = x+y; y = x-y; raise FIB(x, y); }}.P2.LP.CW FIBis a declared exception that returns two integers. The values are supplied when raising the exception:.P1raise FIB(3, 4);.P2When caught the values can be recovered by treating the declared exception identifieras if it were a tuple of 2 integers:.P1(x, y) = e;.P2In general each exception alternative treats the exception identifier appropriately : as a stringwhen the exception qualifier is a string, as the relevant tuple when the exception is declared..LPIf you do.P1"abcde" or FIB => (x, y) = e; sys->print("%s\en", e);.P2you will get a compiler error as.CW e 'stype is indeterminate within this alternative..LPReraising is the same as in the case of string exceptions..LPNote also the difference between the string guard\&\f5"*"\fP and the guard.CW *inthe function fibonacci.The former will match any string exception, the latter any exception. If astring exception does occur it matches the former as it is the most specific.If an unexpected user definedexception occurs it matches the latter..LPThe main difference between declared exceptions and string exceptions isthat the former must be caught by the immediate caller of a function thatraises them, otherwise they turn into a string exception whose name is derivedfrom that of the exception declaration..NH 2The.CW raisesclause.LPThe definition of the function fib in the above example also lists the user defined exceptions it can raise via the use of a.CW raisesclause. In this case there is just the one exception (\f5FIB\f1). Theseclauses (if given) must be compatible between any declaration and definition of the function..LPThe compiler reports instances of functions which either raise some exception whichis not mentioned in their raises clause or does not raise some exception which ismentioned in their raises clause. Currently the report is a warning..NH 1Exponentiation.LPThe exponentiation operator (written as.CW ** )is now part of the Limbo language.Its precedence is above that of multiplication, division and modulus but belowthat of the unary operators. It is right associative. Thus.P13**4*2 = (3**4)*2 = 81*2 = 162-3**4 = (-3)**4 = 812**3**2 = 2**(3**2) = 2**9 = 512.P2The type of the left operand must be.CW int ,.CW bigor.CW real .The type of the right operand must be.CW int .The type of the result is the type of the left operand..NH 1Fixed point types.LPA declaration of the form.P1x: fixed(0.2, 12345.0);.P2declares.CW xto be a variable of a fixed point type. The scale of the type is1/5 and the maximum absolute value of the type is 12345.0..LPSimilarly.P1x: fixed(0.125, 4096.0).P2specifies a scale of 0.125 and a maximum absolute value of 4096.This requires only 17 bits so the underlying type will be.CW intand the compileris free to allocate the remaining 15 bits to greater range or greateraccuracy. In fact the compiler always chooses the latter..LPThe maximum absolute value is optional :-.P1x: fixed(0.125);.P2is equivalent to.P1x: fixed(0.125, 2147483647.0 * 0.125);.P2and ensures the underlying type is exactly an int ie the compiler hasno scope to add any extra bits for more accuracy..LPA binary fixed point type with 8 bits before the binary point and 24 aftermight therefore be declared as.P1x: fixed(2.0**-24);.P2.LPThe scale must be static: its value known at compile time andit must be positive and real; similarly for the maximum absolutevalue when specified..LPCurrently the only underlying base type supported is.CW int ..LPA shorthand for fixed point types is available through the use of.CW typedeclarations:.P1fpt: type fixed(2.0**-16);.P2We can then do.P1x, y, z: fpt;zero: con fpt(0); x = fpt(3.21);y = fpt(4.678);z = fpt(16r1234.5678);z = -x;z = x+y;z = x-y;z = x*y;z = x/y;sys->print("z=%f", real z);.P2There is no implicit numerical casting in Limbo so we have to use explicitcasts to initialize fixed point variables. Note the use of a base toinitialize.CW zusing a new literal representation..LPGiven.P1fpt1: type fixed(0.12345);x: fpt1;fpt2: type fixed(0.1234);y: fpt2;fpt3: type fixed(0.123);z: fpt3;.P2then.P1z = x*y;.P2is illegal. We must add casts and do.P1z = fpt3(x)*fpt3(y);.P2ie type equivalence between fixed point types requires equivalence of scale(and of maximum absolute value when specified)..LPFixed point types may be used where any other numerical type (byte, int, big, real) can be used. So you can compare them, have a list of them, have a channel of them, cast them to or from string and so on..LPYou cannot use complement(~), not(!), and(&), or(|), xor(^) or modulus(%) on them as fixed point types are basically a form of real type..NH 2Accuracy.LPA fixed point value is a multiple of its scale. Given fixed point values X, Y andZ of scale s, t and u respectively, we can write.P1X = sxY = tyZ = uz.P2where x, y and z are integers..LPFor the multiplication Z = X*Y the accuracy achieved is given by.P1| z - (st/u)xy | < 1.P2and for the division Z = X/Y.P1| z - (s/(tu))x/y | < 1.P2That is, the result is always within the result scale of the correct real value..LPThis also applies when casting a fixed point type to another, casting aninteger to a fixed point type and casting a fixed point type to an integer. Theseare all examples of the multiplication law with t = y = 1 since aninteger may be thought of as a fixed point type with a scale of 1.
.TL
Addendum to
.I "The Limbo Programming Language"
.AU
Vita Nuova
.br
30 March 2005
.NH 1
Introduction
.LP
This addendum provides a brief summary of several language changes to
Limbo since
.I "The Limbo Programming Language"
was last revised:
.RS
.IP •
buffered channels
.IP •
unrestricted \f5alt\f1
.IP •
function references
.IP •
exceptions
.IP •
exponentiation
.IP •
fixed-point types
.RE
.NH 1
Buffered channels
.LP
A buffered channel can now be declared:
.P1
c := chan[1] of int;
.P2
Here the buffer size is 1. A send on this channel will succeed immediately
if there is a receiver waiting or if the buffer is empty. A receive on this
channel will succeed immediately if there is a data item in the buffer. This allows us to
write a very simple locking mechanism:
.P1
acquire(c: chan of int)
{
c <-= 0;
}
release(c: chan of int)
{
<-c;
}
new(): chan of int
{
return chan[1] of int;
}
.P2
The declaration
.P1
c := chan[0] of int;
.P2
is equivalent to
.P1
c := chan of int;
.P2
An attempt to create a channel with a negative buffer size will raise
an exception. An attempt to create a channel with a very large buffer
may result in an immediate memory exception if there is not enough
room for the buffer.
.NH 1
Unrestricted
.B alt
.LP
The implementation has changed to remove the restriction that only one process can be
waiting in an
.CW alt
to send or receive on a particular channel.
The busy exception never occurs now. Thus you can do
.P1
i()
{
c := chan of int;
c1 := chan of int;
spawn p(c, c1);
<-c;
spawn p(c, c1);
<-c;
for(i := 0; i < 20; i++)
c1 <-= i;
}
p(c: chan of int, c1: chan of int)
{
c <-= 0;
for(;;)
alt{
i := <-c1 =>
;
}
}
.P2
The two spawned processes can both wait on
.CW c1
without fuss.
Processes are queued on a strict FIFO basis so, in
the example above, the two processes receive on
.CW c1
alternately.
.NH 1
Function references
.LP
Function references may be declared as follows:
.P1
fp: ref fn(s1: string, s2: string): int;
.P2
Given the function
.P1
cmp(s1: string, s2: string): int
{
if(s1 < s2)
return -1;
if(s1 > s2)
return 1;
return 0;
}
.P2
a reference to it can be created by assignment:
.P1
fp = cmp;
.P2
where the name can be qualified by an explicit module reference as usual:
.P1
fp = mod->cmp;
.P2
or it can be returned from a function:
.P1
Cmp: type ref fn(s1: string, s2: string): int;
rcmp(s1: string, s2: string): int
{
return -cmp(s1, s2);
}
choose(i: int): Cmp
{
if(i)
return rcmp;
return cmp;
}
.P2
(the declaration of the synonym
.CW Cmp
was done only for clarity).
They may be declared and passed as parameters:
.P1
sort(a: array of string, f: ref fn(s1, s2: string): int): array of string
{
# ...
}
# ...
b := sort(a, cmp);
c := sort(a, rcmp);
.P2
The function is called via the reference by
.P1
r := fp("fred", "bloggs");
.P2
Otherwise function references behave just like any other reference type.
.NH 1
Exceptions
.LP
Both string exceptions and user defined exceptions are now provided.
The
.CW Sys
module interface to exceptions
has been removed and replaced by new language constructs in limbo.
.NH 2
String exceptions
.LP
Simple string exceptions can be raised as follows
.P1
raise \fIs\fP;
.P2
where
.I s
is any value of type string (it need not be constant).
.LP
Exception handlers may be attached to a block (or sequence of statements) :-
.P1
{
foo();
bar();
} exception e {
"a" or "b" =>
sys->print("caught %s\en", e);
raise;
"ab*" =>
sys->print("caught %s\en", e);
exit;
"abcd*" =>
sys->print("caught %s\en", e);
raise e;
"a*" =>
sys->print("caught %s\en", e);
raise "c";
"*" =>
sys->print("caught %s\en", e);
}
LL:
.P2
.LP
Any exception occurring within the block (and in nested function calls within the block) can
potentially be caught by the exception handler. An exception is caught by a guard exactly
maching the exception string or by a guard
\f5\&"\fP\fIs\fP\f5*"\fP
where
.I s
is a prefix of the exception string.
The most specific match is used. Thus a raise of "a" will be caught by the first
guard and not by the fourth guard. A raise of "abcde" is caught by the third and not the second
or fourth. If a match is found, the sequence of statements following the guard are executed.
If not, the system searches for a handler at a higher level.
.LP
As shown above, the exception is available through the exception identifier (e in this case) if given following the exception keyword.
.LP
The exception is reraised using
.P1
raise;
.P2
or
.P1
raise e;
.P2
.LP
Both the block and the exception code will fall through to the statement labelled
LL unless, of course, they do an explicit exit, return or raise first.
.NH 2
User-defined exceptions
.LP
You can declare your own exceptions:
.P1
implement Fibonacci;
include "sys.m";
include "draw.m";
Fibonacci: module
{
init: fn(nil: ref Draw->Context, argv: list of string);
};
.P3
init(nil: ref Draw->Context, nil: list of string)
{
sys := load Sys Sys->PATH;
for(i := 0; ; i++){
f := fibonacci(i);
if(f < 0)
break;
sys->print("F(%d) = %d\en", i, f);
}
}
.P3
FIB: exception(int, int);
.P3
fibonacci(n: int): int
{
{
fib(1, n, 1, 1);
}exception e{
FIB =>
(x, nil) := e;
return x;
"*" =>
sys->print("unexpected string exception %s raised\en", e);
* =>
sys->print("unexpected exception raised\en");
}
return 0;
}
.P3
fib(n: int, m: int, x: int, y: int) raises (FIB)
{
if(n >= m)
raise FIB(x, y);
{
fib(n+1, m, x, y);
}exception e{
FIB =>
(x, y) = e;
x = x+y;
y = x-y;
raise FIB(x, y);
}
}
.P2
.LP
.CW FIB
is a declared exception that returns two integers. The values are supplied when raising the exception:
.P1
raise FIB(3, 4);
.P2
When caught the values can be recovered by treating the declared exception identifier
as if it were a tuple of 2 integers:
.P1
(x, y) = e;
.P2
In general each exception alternative treats the exception identifier appropriately : as a string
when the exception qualifier is a string, as the relevant tuple when the exception is declared.
.LP
If you do
.P1
"abcde" or FIB =>
(x, y) = e;
sys->print("%s\en", e);
.P2
you will get a compiler error as
.CW e 's
type is indeterminate within this alternative.
.LP
Reraising is the same as in the case of string exceptions.
.LP
Note also the difference between the string guard
\&\f5"*"\fP and the guard
.CW *
in
the function fibonacci.
The former will match any string exception, the latter any exception. If a
string exception does occur it matches the former as it is the most specific.
If an unexpected user defined
exception occurs it matches the latter.
.LP
The main difference between declared exceptions and string exceptions is
that the former must be caught by the immediate caller of a function that
raises them, otherwise they turn into a string exception whose name is derived
from that of the exception declaration.
.NH 2
The
.CW raises
clause
.LP
The definition of the function fib in the above example also lists the user defined exceptions it can raise via the use of a
.CW raises
clause. In this case there is just the one exception (\f5FIB\f1). These
clauses (if given) must be compatible between any declaration and definition of the function.
.LP
The compiler reports instances of functions which either raise some exception which
is not mentioned in their raises clause or does not raise some exception which is
mentioned in their raises clause. Currently the report is a warning.
.NH 1
Exponentiation
.LP
The exponentiation operator (written as
.CW ** )
is now part of the Limbo language.
Its precedence is above that of multiplication, division and modulus but below
that of the unary operators. It is right associative. Thus
.P1
3**4*2 = (3**4)*2 = 81*2 = 162
-3**4 = (-3)**4 = 81
2**3**2 = 2**(3**2) = 2**9 = 512
.P2
The type of the left operand must be
.CW int ,
.CW big
or
.CW real .
The type of the right operand must be
.CW int .
The type of the result is the type of the left operand.
.NH 1
Fixed point types
.LP
A declaration of the form
.P1
x: fixed(0.2, 12345.0);
.P2
declares
.CW x
to be a variable of a fixed point type. The scale of the type is
1/5 and the maximum absolute value of the type is 12345.0.
.LP
Similarly
.P1
x: fixed(0.125, 4096.0)
.P2
specifies a scale of 0.125 and a maximum absolute value of 4096.
This requires only 17 bits so the underlying type will be
.CW int
and the compiler
is free to allocate the remaining 15 bits to greater range or greater
accuracy. In fact the compiler always chooses the latter.
.LP
The maximum absolute value is optional :-
.P1
x: fixed(0.125);
.P2
is equivalent to
.P1
x: fixed(0.125, 2147483647.0 * 0.125);
.P2
and ensures the underlying type is exactly an int ie the compiler has
no scope to add any extra bits for more accuracy.
.LP
A binary fixed point type with 8 bits before the binary point and 24 after
might therefore be declared as
.P1
x: fixed(2.0**-24);
.P2
.LP
The scale must be static: its value known at compile time and
it must be positive and real; similarly for the maximum absolute
value when specified.
.LP
Currently the only underlying base type supported is
.CW int .
.LP
A shorthand for fixed point types is available through the use of
.CW type
declarations:
.P1
fpt: type fixed(2.0**-16);
.P2
We can then do
.P1
x, y, z: fpt;
zero: con fpt(0);
x = fpt(3.21);
y = fpt(4.678);
z = fpt(16r1234.5678);
z = -x;
z = x+y;
z = x-y;
z = x*y;
z = x/y;
sys->print("z=%f", real z);
.P2
There is no implicit numerical casting in Limbo so we have to use explicit
casts to initialize fixed point variables. Note the use of a base to
initialize
.CW z
using a new literal representation.
.LP
Given
.P1
fpt1: type fixed(0.12345);
x: fpt1;
fpt2: type fixed(0.1234);
y: fpt2;
fpt3: type fixed(0.123);
z: fpt3;
.P2
then
.P1
z = x*y;
.P2
is illegal. We must add casts and do
.P1
z = fpt3(x)*fpt3(y);
.P2
ie type equivalence between fixed point types requires equivalence of scale
(and of maximum absolute value when specified).
.LP
Fixed point types may be used where any other numerical type (byte, int, big, real) can be used. So you can compare them, have a list of them, have a channel of them, cast them to or from string and so on.
.LP
You cannot use complement(~), not(!), and(&), or(|), xor(^) or modulus(%) on them as fixed point types are basically a form of real type.
.NH 2
Accuracy
.LP
A fixed point value is a multiple of its scale. Given fixed point values X, Y and
Z of scale s, t and u respectively, we can write
.P1
X = sx
Y = ty
Z = uz
.P2
where x, y and z are integers.
.LP
For the multiplication Z = X*Y the accuracy achieved is given by
.P1
| z - (st/u)xy | < 1
.P2
and for the division Z = X/Y
.P1
| z - (s/(tu))x/y | < 1
.P2
That is, the result is always within the result scale of the correct real value.
.LP
This also applies when casting a fixed point type to another, casting an
integer to a fixed point type and casting a fixed point type to an integer. These
are all examples of the multiplication law with t = y = 1 since an
integer may be thought of as a fixed point type with a scale of 1.