Functions and scope

def, lambda, defaults, closures, and decorators all have a direct spelling. One thing is new: a parameter that refers to the caller’s variable.

Defining

Python

def greet(name, greeting="hello"):
    return f"{greeting} {name}"

Koskari

def greet [name greeting: "hello"]:
  f"{greeting} {name}"

How are keyword defaults written?

name: value in the formals, and name: value at the call: (greet "Ada" greeting: "hi"). A *: rest formal collects extra positionals and **: kwargs collects extra keywords.

Python

lambda x: x + 1

Koskari

(lambda [x] (+ x 1))

Returning

Python

def first_even(xs):
    for x in xs:
        if x % 2 == 0:
            return x
    return None

Koskari

def first-even [xs]:
  for-each [x xs]:
    when (== 0 (% x 2)):
      return x
  None

When do I write return?

To leave early. Otherwise the last form is the result. return with two or more arguments returns a tuple, and var [a b] (f) unpacks it.

Local names

Python

def area():
    w, h = 6, 7
    return w * h

Koskari

def area []:
  let [[w 6] [h 7]]:
    (* w h)

What does let add over var?

Scope. The names exist only inside the let body. A var in a function body is local to the function, as in Python, but a let can scope names to one calculation inside it.

Python

def count_down(n):
    while n > 0:
        print(n)
        n -= 1

Koskari

def count-down [n]:
  let loop [[n n]]:
    when (> n 0):
      print n
      (loop (- n 1))

What is a named let?

A loop written as a function. loop is bound inside the body to a function of the same bindings; calling it is the next iteration. Tail calls do not grow the stack, so this is as cheap as while. Use whichever reads better.

Closures

Python

def make_counter():
    count = 0
    def tick():
        nonlocal count
        count += 1
        return count
    return tick

Koskari

def make-counter []:
  var count 0
  def tick []:
    incr! count
    count
  tick

Where is nonlocal?

Not needed. set! and incr! store into the name they find, and the inner function finds the outer count. var and global exist for the cases where you want to say so explicitly.

By-reference parameters

Python

def bump(box):
    box[0] += 1

value = [10]
bump(value)
value[0]   # 11

Koskari

def bump [&n]:
  incr! n

var value 10
(bump &value)
value   ; 11

What is the &?

A by-reference parameter. &n in the formals says the parameter is the caller’s storage location, not a copy of the value, and &value at the call passes that location. Python has no way to rebind a caller’s variable; the box in the Python column is the usual workaround. See References and storage indirection.

Decorators

Python

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    ...

Koskari

import-from functools lru_cache

@(lru_cache maxsize: None)
@demand
def fib [n]:
  ...

Why the extra @demand?

A Koskari function is ambiently async: called from Koskari it yields a value, but called from plain Python code, which lru_cache is, it yields a coroutine. @demand gives the Python side a function that returns the value. Any Python decorator that calls the function it wraps wants this; a decorator that only registers it does not. The Fibonacci example shows both.

Higher-order functions

Python

list(map(lambda x: x * 2, xs))
sorted(xs, key=lambda x: -x)

Koskari

(list (map (lambda [x] (* x 2)) xs))
(sorted xs key: (lambda [x] (- x)))

Is that Python’s map?

No, Koskari’s, which takes a Koskari function directly. sorted is Python’s and wraps the key for you. Other Python functions that call a callable you pass them, min, max, list.sort, want a demand around a Koskari lambda. See Demand, async, and defer.

Run it

Save as greet.ki:

def greet [name greeting: "hello" punct: "!"]:
  (+ greeting " " name punct)

@entry-point
def main []:
  print (greet "Ada")
  print (greet "Grace" greeting: "Hi" punct: ".")
hello Ada!
Hi Grace.