Elmord's Magic Valley

Software, lingüística e rock'n'roll. Sometimes in English.

Loops and blocks in Hel

2019-04-11 17:11 -0300. Tags: comp, prog, pldesign, hel, fenius, in-english

Hel got a preliminary version of its main looping and non-local control flow primitives today: do, redo and return. They have characteristics similar to Common Lisp's block/return-from, Scheme's named let, and Clojure's loop/recur (and, one might say, Java's labeled blocks, continues and breaks), though they are not exactly the same as any of these. In this post, I will describe how they work.

do

do creates a labeled block with parameters and initial values. It has the syntax:

do name(var1=value1, var2=value2, ...) {
    body
}

What this does is to evaluate body in an environment containing the specified variables with the given values. The variable declaration section is like a function parameter declaration where all parameters are given default values. For example:

do block(x=1, y=2) {
    x+y
}

will return 3.

Within the block, name is bound to a tag for the block. This tag can be used with the redo and return commands.

redo

redo can be used to repeat the named block, with new values for the block variables. For example:

# Print all integers from 1 to `limit`.
let count_until(limit) = {
    do block(i=1) {             # First iteration will run with `i` = 1.
        if i <= limit {         # If we have not reached the limit yet...
            print(i)            # Print the current value of `i`...
            redo block(i=i+1)   # And repeat the block, with a new value of `i`.
        }
    }
}

This is analogous to Clojure's recur, except it does not have to be in tail position, and you can specify the label of the block you want to repeat (so you can have nested blocks and escape to outermost ones).

This is also similar to Scheme's named let, except that the new execution of the block replaces the current one, rather than behaving like a regular function call.

The names of the parameters in the redo call are optional; we could have written redo block(i+1) instead of redo block(i=i+1). This is analogous to the function call syntax.

return

return can be used to return a value immediately from a block. For example, suppose we have a foreach function which takes a list and a function, and applies the function to each element of the list in order:

let foreach(list, f) = {
    do block(list=list) {                # Start iterating with the full list
        if list != [] {                  # If the list is not empty yet...
            f(list.first)                # Apply the function to the first element...
            redo block(list=list.rest)   # And repeat the block for the remaining ones.
        }
    }
}

Now we want to write a function to test if a given element is in a list. We want to reuse foreach to do the iteration, but we want to stop the iteration (and get out of foreach immediately) when we find the element in the list. We can do this with return:

let is_member(searched, list) = {
    do out() {
        # Call `foreach` with an anonymous function, which will be called
        # for each element of the list.
        foreach(list, fn (item) {
            if item == searched {        # If the current element is the one we are searching...
                return true from out     # Return `true` immediately from the `out` block.
            }
        })
        # If we got here, it's because the element was not found.
        return false from out
    }
}

These constructs can be compared to Java's labeled blocks, continues and breaks. However, Hel blocks take parameters, which must be specified when redoing them (the equivalent of continue); and Hel blocks return a value, which must be specified when returning from them (the equivalent of break).

To do / open questions

Common Lisp has the notion of a default block (which is the block labelled nil). Some constructs, like return, return from the default block, so you can avoid naming the block if you are only using one. It would be nice to have something similar in Hel.

Currently the parameter/argument binding logic for blocks is the same one used for functions. This means that if one of the block's arguments is omitted from the redo call, it will acquire the initial value specified in the beginning of the block! This is most likely not what you want. Alternative behaviours would be to forbid omitting block arguments, or reusing the value in the current iteration rather than the initial value.

Perhaps instead of using special forms for redo and return, these could be methods of the tag object, so we would write, for example, block.redo(i=i+1) instead of redo block(i=i+1), and block.return(42) rather than return 42 from block. I like the special form better, especially for redo because the block name stays together with the arguments just like in the block declaration. It also allows the possibility of omitting the block name if we get default blocks in the future.

Comentários / Comments (0)

Deixe um comentário / Leave a comment

Main menu

Posts recentes

Comentários recentes

Tags

comp (129) prog (64) life (45) in-english (38) pldesign (32) unix (32) lang (30) random (28) about (25) mind (23) lisp (22) mundane (21) fenius (17) web (17) ramble (16) img (13) hel (12) rant (12) privacy (10) scheme (9) freedom (8) esperanto (7) bash (7) academia (7) copyright (7) music (7) lash (7) mestrado (6) shell (6) home (6) misc (5) conlang (5) php (4) editor (4) latex (4) book (4) politics (4) etymology (4) worldly (4) security (3) kbd (3) film (3) tour-de-scheme (3) network (3) c (3) android (3) emacs (3) wrong (3) poem (2) cook (2) physics (2) llvm (2) lows (2) comic (2) treta (2) perl (1) wm (1) audio (1) german (1) kindle (1) pointless (1) philosophy (1)

Elsewhere

Quod vide


Copyright © 2010-2019 Vítor De Araújo
O conteúdo deste blog, a menos que de outra forma especificado, pode ser utilizado segundo os termos da licença Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International.

Powered by Blognir.