Functions
Definition, formals, results, closures, recursion, and the two spellings of an anonymous function.
Defining
The layout form and the parenthesized form are the same definition:
def double [n]:
(* n 2)
(def double [n]
(* n 2))
A body that fits on the header line needs no colon:
def double [n] (* n 2)
def is sugar for binding a function value to a name. lambda makes
the same value without a name:
(list (map (lambda [x] (+ x 1)) #[1 2 3])) ;; #[2 3 4]
Functions are values. Pass them, store them, return them.
Formals
The formals list holds positional names, keyword names with defaults, and the two rest collectors:
def connect [host port: 80 *: extra **: options]:
#(host port extra options)
(connect "a" 8080 1 2 verbose: True)
;; #("a" 8080 #(1 2) #{"verbose": True})
nameis positional and required.name: defaultis a keyword with a default, passed asname: value.*: restcollects extra positionals into a tuple.**: optionscollects extra keywords into a dict.
A &name formal is by reference: it aliases the caller’s storage
instead of copying the value, so set! inside the function writes back
to the caller. It has its own page,
References and storage indirection.
Annotations attach with :: and land in __annotations__ as real
objects; see Types and annotations.
Results
A body runs in order and its last form is the result:
def shipping [subtotal]:
if (>= subtotal 50):
0
else:
5
return leaves early. It is a call, so (return) and (return x) are
the spellings, and a bare return on a layout line expands to
(return):
def first-even [xs]:
for-each [x xs]:
when (== 0 (% x 2)):
return x
None
return is variadic. Two or more positional arguments return a tuple;
keyword arguments return a values object:
def minmax [a b]:
if (< a b):
return a b
else:
return b a
var [lo hi] (minmax 8 3) ;; lo = 3, hi = 8
def stats [xs]:
return count: (len xs) first: xs[0]
(stats #[10 20 30]) ;; (values count: 3 first: 10)
Positional and keyword arguments cannot be mixed in one return.
Destructure a tuple result with
var over a pattern or
let-values.
Closures
A function closes over the namespace it was defined in. An inner
function reads outer names, and set! inside it stores into the outer
name it finds, with no declaration:
def make-counter []:
var count 0
def tick []:
incr! count
count
tick
var t (make-counter)
(t)
(t) ;; 2
var declares a name in the current scope and global var declares it at
the module, saying explicitly which scope a store should reach; see
var and
global.
Recursion and named let
A recursive function calls itself. Write the stopping case first:
def countdown [n]:
if (<= n 0):
print "done"
else:
print n
(countdown (- n 1))
Tail calls do not grow the stack, so a loop written as recursion costs
what a while costs. The named let is the idiom for that:
def fib [n]:
let loop [[n n] [a 0] [b 1]]:
if (< n 1):
a
else:
(loop (- n 1) b (+ a b))
loop is bound inside the body to a function of the same bindings, and
calling it is the next iteration.
Decorators
An @ line above a def applies a decorator, as in Python. Koskari
adds decorator forms that wrap the definition syntactically, such as
@let closing a function over a private binding. The whole surface is
in Decorators.
Calling from Python
A Koskari function called from Koskari yields a value. Called from
plain Python code, with no event loop, it yields a coroutine, because
every Koskari function can suspend. @demand over a def gives
Python a function that returns the value. That
applies to any function a Python caller invokes directly, including a
Python decorator like lru_cache that calls what it wraps. The model
is in Demand, async, and defer.
Thunks
Braces make a short anonymous function whose formals are inferred from
%1, %2, and so on:
(list (map {(* %1 %1)} #[1 2 3])) ;; #[1 4 9]
{(* %1 %1)} is (lambda [%1] (* %1 %1)). Use it for a one-expression
callback; name anything longer.