📜 ⬆️ ⬇️

Limbo

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:

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:

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; #      

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"); #  i_r_s_s  (int, real, string, string) i_r_s_s := (5, 0.5, "five", "comment"); 

Moreover, a tuple can be “parsed” 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, 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; #   c <-= 10; #    i := <-c; #    int <-c; #     c = nil; #   

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).

Source: https://habr.com/ru/post/8974/


All Articles