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

    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

    stringcan 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

    listit 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 type
    • hd - returns the first element of the list without changing the list itself
    • tl- 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

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

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

    Also popular now: