smolka

#Functions

2 minute read

drizzle now supports functions and starts to look like a proper programming language. Its syntax is similar to an unknown snake-like language used in niche fields like machine learning. Keeping in line with that theme, here is how a function is defined:

def sum(a, b):
  return a + b

Simple, I know. But I still made some mistakes along the way. Consider this example:

def outer():
  def inner():
    noop
  return inner

outer()()

Here we first call the outer and then the returned inner function. The parser produces a Call expression, and the compiler then turns it into opcodes that push callee and arguments onto the stack.

 struct Call {
-  Identifier callee;
+  Expr callee;
   Exprs arguments;
 };

The initial implementation expected an identifier for the callee. That worked for simple cases, but using an expression is the correct way to do it. The syntax tree reflects that change and properly displays chained function calls.

program
  def outer
    def inner
      noop
    return
      variable inner
  expression_statement
    call
      call
        variable outer

##Closures

Programming languages with first-class functions support closures. They are used to bind the surrounding lexical scope and make sure that referenced variables have the same lifetime as the function using them.

def make_counter():
  var i = 0
  def counter():
    i = i + 1
    return i
  return counter

var counter = make_counter()
counter()  # Returns 1
counter()  # Returns 2

The example would work if closures were implemented, but drizzle throws an error:

Line 4 |     i = i + 1
                 ^
SyntaxError: cannot capture local variable

Closures add quite some complexity to the code that I am not willing to introduce, and to be honest: I do not need them. I tried some magic tricks to achieve a minimalistic version, but all of them fell apart when it came to recursion. The best I could do were absolute stack offsets to access global variables.