Limbo
Since Inferno attracts me precisely as a development environment, in addition to the architecture of the system itself, the programming language is of considerable importance.
By and large, I do not care for a long time what language to write in (I have been programming since 1989, and during this time I tried a bunch of languages). But ... all the same, it is more pleasant to work in some languages than in others - and the point here is 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. The languages are completely different: Perl is not typed at all, Limbo is strongly typed; Perl does not have normal thread support and asynchrony has to be achieved through multiplexing, Limbo - almost forces you to write multithreaded programs (if you watchedRob Pike’s presentation , there was a cool example with multi-threaded search for primes); etc. Nevertheless, I really liked Limbo and I started to write working code on it almost immediately.
I don’t remember C very well, but I’ll 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! :)).
About such features of Limbo as the similarity of syntax with C, high portability of byte code, sharpening for parallel programming, dynamic loading / unloading of modules, checking the types and boundaries of arrays, including during execution, and the presence of a garbage collector, I already mentioned.
You can also add that a significant number of various libraries (included with Inferno) have been written for Limbo that make it easier to work with graphics, mathematics, databases, etc.
To understand the examples, it is worth adding that the variable type declaration is made in Pascal style:
In addition to the usual numeric types, structures, and union, Limbo supports strings and several more specific data types: lists, arrays, tuples, and pipes. (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 not of interest.) All these data types are first-class variables, i.e. they can be stored in variables, transmitted through channels, etc.
Regular numeric types can be converted to each other, in addition, strings can also be converted to numbers and vice versa. But all conversions must be specified explicitly; there are no implicit type conversions.
In addition, strings support slices, i.e. You can refer to a specific character or sequence of characters, such as:
There are three operators for working with lists:
Example:
It’s clear why working with lists is limited to such operations - they are very easy to implement efficiently, and such lists will work very quickly. And, indeed, quite often it is necessary to work with structures that fit perfectly with the existing functionality.
The size of the array is indicated 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 size of the array has become known).
In fact, in Limbo there are only two ways to dynamically allocate memory: create an array by specifying the required size through a variable, and add a new element to the beginning of the list.
Naturally, arrays also support slices.
Moreover, tuple can be "disassembled" into its components by assigning it to the list of ordinary variables:
By the way, exchanging the values of two variables on Limbo is done something like this:
Channels (
Reading / writing a channel is a blocking operation. Read / write statements look like arrows:
Channels are buffered (you specify the size of the buffer in the same way as the size of the array). Writing to buffered channels is not blocked until the buffer is full. The buffer works as a FIFO queue.
For multiplexing channels, Limbo has two means - you can read from an array of channels, or you can use the special operator alt to select a channel.
In fact, channels are the only IPC method in Limbo, they are used both for data transmission and for synchronizing streams, in general, a complete replacement for all mutexes, semaphores, shared memory, etc ...
As for their performance ... when transmitting something through a channel, it’s just transmitted memory address, i.e. no copying actually happens and everything just flies.
This is an array that stores the channels through which tuple consisting of int and a list of strings are transmitted. The size of the array is not determined here, it will be set at runtime, when the array is initialized.
Limbo uses UTF8 for I / O, and UTF16 for representing strings in memory.
That is, for example, when reading the source code of a module from a disk, UTF8 can be used in it in comments, lines, and symbolic constants.
If there is an array of bytes (
Functions can be passed parameters to function links.
Objects are simulated through the data type structure (
To run a given function in a separate thread, Limbo uses the built-in spawn operator.
Exception support is available for both standard string and custom types. Unfortunately, most system and library functions use tuple: instead of exceptions to return errors
Well, for a snack, a full description of Limbo in Russian . In fact, this is approximately 99.9% complete retelling of the English docks by Limbo in their own words and otherwise structured (I, as a Perl programmer, wanted to focus on data types and operations on them, otherwise I managed to break the habit of typed languages).
By and large, I do not care for a long time what language to write in (I have been programming since 1989, and during this time I tried a bunch of languages). But ... all the same, it is more pleasant to work in some languages than in others - and the point here is 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. The languages are completely different: Perl is not typed at all, Limbo is strongly typed; Perl does not have normal thread support and asynchrony has to be achieved through multiplexing, Limbo - almost forces you to write multithreaded programs (if you watchedRob Pike’s presentation , there was a cool example with multi-threaded search for primes); etc. Nevertheless, I really liked Limbo and I started to write working code on it almost immediately.
I don’t remember C very well, but I’ll 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 the similarity of syntax with C, high portability of byte code, sharpening for parallel programming, dynamic loading / unloading of modules, checking the types and boundaries of arrays, including during execution, and the presence of a garbage collector, I already mentioned.
You can also add that a significant number of various libraries (included with Inferno) have been written for Limbo that make it easier to work with graphics, mathematics, databases, etc.
To understand the examples, it is worth adding that the variable type declaration is made in Pascal style:
:
- ad=
- assignment:=
- declaration with simultaneous assignment, the type is determined by the type of the assigned object
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 pipes. (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 not of interest.) All these data types are first-class variables, i.e. they can be stored in variables, transmitted through channels, etc.
Regular numeric types can be converted to each other, in addition, strings can also be converted to numbers and vice versa. But all conversions must be specified explicitly; there are no implicit type conversions.
Lines
string
can be converted to byte arrays, and vice versa. In addition, strings support slices, i.e. You can refer to a specific character or sequence of characters, such as:
my_string[5:15]
.Lists
list
it is a sequence of elements of the same 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 for 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 a given list - i.e. "Bites" the first element
Example:
l : list of int;
l = 10 :: 20 :: 30 :: nil; # создаём список из 3-х элементов
l = 5 :: l; # добавляем в начало ещё один
i := hd l; # получаем int равный 5, список не изменился
l2 := tl l; # получаем новый список 10 :: 20 :: 30 :: nil
l2 = tl l2; # удаляем из него первый элемент
It’s clear why working with lists is limited to such operations - they are very easy to implement efficiently, and such lists will work very quickly. And, indeed, quite often it is necessary to work with structures that fit perfectly with the existing functionality.
Arrays
array
contains a fixed number of elements of the same type. The size of the array is indicated 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 size of the array has become known).
In fact, in Limbo there are only two ways to dynamically allocate memory: create an array by specifying the required size through a variable, and add a new element to the beginning of the list.
Naturally, arrays also support slices.
Tuples (tuples)
tuple
it is something like a list of 2 or more elements of any type. And this is not just a list, but the same data type as others - the type of 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");
# тип i_r_s_s это (int, real, string, string)
i_r_s_s := (5, 0.5, "five", "comment");
Moreover, tuple can be "disassembled" into its components by assigning it to the list of ordinary variables:
# создаёт переменные i типа int и s типа string и
# инициализирует их значениями 5 и "five"
(i, s) := i_s;
By the way, exchanging the values of two variables on Limbo is done something like this:
(i, j) = (j, i);
Channels
Channels (
chan
) allow you to organize IPC between local processes by transferring atomic objects of a given type. Reading / writing a channel is a blocking operation. Read / write statements look like arrows:
c := chan of int; # создаёт канал
c <-= 10; # отправить в канал
i := <-c; # принять из канала int
<-c; # принять и проигнорировать значение
c = nil; # уничтожить канал
Channels are buffered (you specify the size of the buffer in the same way as the size of the array). Writing to buffered channels is not blocked until the buffer is full. The buffer works as a FIFO queue.
For multiplexing channels, Limbo has two means - you can read from an array of channels, or you can use the special operator alt to select a channel.
alt {
i := <-inchan =>
sys->print("received: %d\n", i);
outchan <-= "message" =>
sys->print("message sent\n");
}
In fact, channels are the only IPC method in Limbo, they are used both for data transmission and for synchronizing streams, in general, a complete replacement for all mutexes, semaphores, shared memory, etc ...
As for their performance ... when transmitting something through a channel, it’s just transmitted memory address, i.e. no copying actually happens and everything just flies.
Compound Types
cool : array of chan of (int, list of string);
This is an array that stores the channels through which tuple consisting of int and a list of strings are transmitted. The size of the array is not determined here, it will be set at runtime, when the array is initialized.
Unicode
Limbo uses UTF8 for I / O, and UTF16 for representing strings in memory.
That is, for example, when reading the source code of a module from a disk, UTF8 can be used in it in comments, lines, and symbolic constants.
If there is an array of bytes (
array of byte
) and it is converted to a string, then bytes from the array are processed as UTF8 and converted to a string in UTF16; and when converting a string into a byte array, the inverse is converted and UTF8 is in the array.Functions
Functions can be passed parameters to function links.
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, of course, a very castrated OOP - there is no inheritance, nothing, populated by robots. (c) :) However, I'm lying. Polymorphism - is. But a little 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
Exception support is available for both standard string and custom types. Unfortunately, most system and library functions use tuple: instead of exceptions to return errors
(errcode, result)
. Of course, tuple is a big step forward regarding the POSIX return of error information in the form of a -1 result, but ... I would like to use exceptions instead.References
Well, for a snack, a full description of Limbo in Russian . In fact, this is approximately 99.9% complete retelling of the English docks by Limbo in their own words and otherwise structured (I, as a Perl programmer, wanted to focus on data types and operations on them, otherwise I managed to break the habit of typed languages).