Do block

Do blocks are ad-hoc methods. They are similar to methods in that they represent parametrized blocks of code. Unlike methods, do blocks are expressions, so they are treated as values rather than declarations. On their own, they do not have a name we can refer to, nor means of invoking them.

In other languages, do blocks appear under names like 'function expression' or 'lambda'.

Do blocks are usually used when a function or a method needs a function or a method as an argument.

Cheats

  • do blocks compile to function expressions in JavaScript.
  • The do block syntax is do |x, y, z| body.
  • Parameters delimited by bars | | are optional.
  • The body can be one line or multiple lines.
  • self inside a do block refers to the self in the outer scope.
  • this inside a do block is a normal this.
  • Named function declarations are written similarly to methods, but with var def instead of def.

Writing do blocks

The syntax of the do block looks very different compared to methods, but the basic structure is the same.

Here is a method:

1
2
def add x, y
    x + y

Here is an equivalent do block:

1
var add = do |x, y| x + y

Do blocks have all the features that methods do, like optional and rest parameters.

Using do blocks as callbacks

Do blocks are most commonly used as callbacks in methods and functions. While declared methods or imported functions can also be used as callbacks, do blocks have an advantage of being ad-hoc. They can be defined on the spot as one-off callbacks.

Here is an example of using a method as a callback:

1
2
3
4
def afterSave assets
    game.start assets, config

loadAssets self:afterSave

If we rewrite the above using a do block it looks like this:

1
loadAssets do |assets| game.start assets, config

Using & placeholder

In some cases callbacks are not conveniently the last parameter. The setTimeout and setInterval built-in functions are good examples.

1
2
3
setInterval do
    gameTimer.update Date.now
, 100

The & character can be used as a placeholder in such cases, to allow us to supply the callback last. This requires parentheses around all parameters.

1
setInterval(&, 100) do gameTimer.update Date.now

Note

If you come from a functional programming background, this may look like partial application. During compilation, Imba actually replaces the placeholder with the callback in the resulting code so it's not doing any partial application, and there is no penalty for using this syntax.

Using do blocks to write higher order methods

Higher order functions are functions that take functions as arguments or return them. We have already seen an example of a higher order function that take other functions as arguments in the setInterval example in this section, where we use a do block as an argument.

Do blocks can also be very useful when we want to write methods that are higher order functions (a.k.a. higher order methods). Because declaring a method also creates a property on the self object, declaring new methods inside other methods may sometimes yield unexpected results, and can create properties we don't really need. Because of this, it is usually preferred to use do blocks instead.

Consider the following method.

1
2
3
4
5
6
7
8
def drawShape screen, shape
    screen.save
    screen.draw shape
    screen.restore

drawShape screen, triangle
drawShape screen, rectangle
drawShape screen, machoDudeWithTagsAsArms

If we use this method throughout our program, and there is only one screen that we draw to, it may seem like a bad idea to keep supplying screen object every time we call drawShape. We can solve this by converting the drawShape into a higher order method:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def drawShape screen
    do |shape|
        screen.save
        screen.draw shape
        screen.restore

var drawToScreen = drawShape screen

drawToScreen triangle
drawToScreen rectangle
drawToScreen machoDudeWithTagsAsArms

Note

The technique in the last example is called currying.

Do blocks and self

The self object inside a do block always refers to the self in the outer scope.

1
2
3
4
5
6
7
def keyHandlerFor code
    # ....

def attachEventHandler target
    target.addEventListener 'keydown', do |event|
        var keyHandler = self.keyHandlerFor event.keyCode
        keyHandler

In this example, the self.keyHandler refers to a method on the same self object on which attachEventHandler is declared.

Note

You probably notice that we do not really need to use self. in the last example. It's just there to make it explicit and clear. Even if we omit self., it makes no difference because Imba always treats undeclared names as methods on the self object.

Function declarations

In the very first example in this section, we have seen a do block that was assigned to a variable. This pattern can be written using var def declarations.

Although var def declarations look similar to method declarations, they are semantically closer to do blocks.

Let's take a look at the first example again:

1
var add = do |x, y| x + y

Here is a version that is rewritten using var def.

1
2
var def add x, y
    x + y

Warning

Function declarations are not subject to hoisting in Imba even if it may seem so just looking at the compiler output. Calling a var def function above the point of declaration will cause the compiler to treat the reference as a method call on the self object.

The example of the drawShape higher order method can also be rewritten using function declarations:

1
2
3
4
5
def drawShape screen
    var def draw shape
        screen.save
        screen.draw shape
        screen.restore

There is no practical difference between the original drawShape and this one in this particular case, but the main difference is that the inner function has a name, unlike do blocks. This is an important property when it comes to recursive functions.