Since Inferno attracts me precisely as a development environment, besides the architecture of the system itself, a programming language is of considerable importance.
By and large, I do not care for a long time what language to write (I’ve been programming since 1989, and during that time I tried a bunch of languages). But ... all the same, it is more pleasant to work on some languages than on others - and here it’s not that some languages are better than others, but that different languages are better suited for different styles of thinking.
The transition from Perl to Limbo is very contrasting. Languages are completely different: Perl is not typed at all, Limbo is strongly typed; in Perl, there is no normal support for threads and asynchrony; one has to achieve through multiplexing, Limbo almost makes it necessary to write multithreaded programs (if you watched
Rob Pike’s presentation , there was a cool example of multithreaded prime search); etc. And, nevertheless, I really liked Limbo and I started to write working code on it almost immediately.
')
I do not remember C very well, but I will try to describe Limbo precisely in terms of differences from C - I think it will be easier for most of the audience (and not a word about PHP! :)).
general information
About such features of Limbo as syntax similarity with C, high portability of bytecode, sharpness for parallel programming, dynamic loading / unloading of modules, checking types and borders of arrays, including during execution and the presence of garbage collection, I have already mentioned.
You can also add that for Limbo a significant number of various libraries were written (included with Inferno), which facilitate the work with graphics, mathematics, databases, etc.
To understand the examples, it is worth adding that the declaration of the type of the variable is done in Pascal style:
:
- announcement=
- assignment:=
- declaration with simultaneous assignment, the type is determined by the type of object being assigned
Data types
In addition to the usual numeric types, structures and union, Limbo supports strings and several more specific data types: lists, arrays, tuples and channels. (There is also a special type of “module”, I mentioned it earlier when I described interfaces, but from the point of view of language features, it is of no interest.) All these data types are first-class variables, i.e. they can be stored in variables, passed through channels, etc.
Ordinary numeric types can be converted into each other, besides strings can also be converted into numbers and vice versa. But all conversions must be specified explicitly, there is no implicit type conversion.
Strings
string
can be converted to byte arrays, and vice versa.
In addition, the lines support slices, i.e. you can refer to a specific character or sequence of characters, for example:
my_string[5:15]
.
Lists
list
is a sequence of elements of one type optimized for stack-like operations (add an element to the top of the list, get the first element of the list, get the rest of the list (except the first element)).
There are three operators for working with lists:
::
- creating a new list, the left operand is one element, the right one is a list of elements of the same typehd
- returns the first element of the list without changing the list itselftl
- returns a list consisting of the second and subsequent elements of the specified list - i.e. "Bites out" the first element
Example:
l : list of int; l = 10 :: 20 :: 30 :: nil;
Why working with lists is limited to such operations is clear - they are very easy to implement effectively, and such lists will work very quickly. And, indeed, quite often it is necessary to work with structures that fit well with the existing functionality.
Arrays
array
contains a fixed number of elements of the same type.
The size of the array is specified when it is created / initialized, and not when the type of the variable is declared - i.e. Arrays can be dynamically created at any time (when the required array size is known).
In fact, in Limbo there are only two ways to dynamically allocate memory: create an array by specifying the required size via a variable, and add a new element to the beginning of the list.
Naturally, arrays also support slices.
Tuples (tuples)
tuple
is something like a list of 2 or more items of any type. And this is not just a list, but the same data type as the others - the type of the tuple itself is actually determined by what types of elements and in what order it contains. Example:
i_s : (int, string); i_s = (5, "five");
Moreover, a tuple can be “parsed” into its components by assigning it to the list of ordinary variables:
By the way, the exchange of values of two variables on Limbo is done like this:
(i, j) = (j, i);
Channels
Channels (
chan
) allow IPC to be organized between local processes by transferring atomically objects of a given type.
A read / write channel is a blocking operation. Read / write statements look like arrows:
c := chan of int;
Channels are buffered (you specify the buffer size in much the same way as the array size). Writing to buffered channels is not blocked until the buffer is full. The buffer works as a FIFO queue.
For multiplexing channels in Limbo there are two whole means - you can read from the channel array, or you can use the special alt operator to select a channel.
alt { i := <-inchan => sys->print("received: %d\n", i); outchan <-= "message" => sys->print("message sent\n"); }
Actually, channels are the only way for IPC in Limbo, they are used both for data transfer and for synchronization of streams, in general, a complete replacement with every mutexes, semaphores, shared memory, etc ...
As for their performance ... when transmitting something through a channel, its address in memory is transmitted, i.e. no copying actually happens and everything just flies.
Composite types
cool : array of chan of (int, list of string);
This is an array storing the channels through which tuples consisting of int and a list of strings are transmitted. The size of the array is not defined here, it will be set during execution, when the array is initialized.
Unicode
Limbo uses UTF8 for I / O, and UTF16 to represent strings in memory.
That is, for example, when reading a module source from a disk, UTF8 can be used in it in comments, lines, and character constants.
If there is an array of bytes (
array of byte
) and it is converted into a string, then the bytes from the array are processed as UTF8 and converted into a string in UTF16; when converting a string to an array of bytes, the inverse transformation occurs and UTF8 appears in the array.
Functions
Functions can be passed parameters parameters to functions.
OOP
Objects are simulated through the data type structure (
adt
), the elements of which in addition to the usual data types can be functions. In fact, this is certainly a very neutered OOP - there is no inheritance, nothing, it is populated by robots. (c) :) However, I'm lying. Polymorphism - is. But a bit strange, more like templates in C ++:
see for yourself .
Threads
To run a given function in a separate thread, Limbo uses the built-in spawn operator.
Errors and exceptions
There is support for exceptions of both ordinary string and custom types. To my regret, most of the system and library functions instead of exceptions to return errors use tuple:
(errcode, result)
. Of course, tuple is a big step forward regarding the POSIX return of error information as a result of -1, but ... I would like to use exceptions instead.
Links
Well, for a snack, a
full description of Limbo is in Russian . In fact, this is approximately 99.9% complete retelling of English-language docks by Limbo in one's own words and otherwise structured (I, as a Perl programmer, wanted to focus on data types and operations on them, otherwise I managed to get rid of typed languages).