Data and control

Values and control flow are where Python habits carry the furthest.

Atoms

Python

42
-3
"hello"
True, False, None

Koskari

42
-3
"hello"
True False None

What is nil?

The empty Lisp list, the end of every pair chain. It is not None: (nil? nil) is true and (None? nil) is false. Python data never produces one; you meet it with quoted lists and macros.

Collections

Python

[1, 2, 3]
(1, 2, 3)
{"a": 1, "b": 2}

Koskari

#[1 2 3]
#(1 2 3)
#{"a": 1 "b": 2}

Why the #?

Bare [1 2 3] reads like (1 2 3), a call of 1. The # marks a literal. Keys in #{...} are expressions, so a string key is quoted; a bare a: key would look up the variable a.

Python

[*xs, 1, 2]
(1, *xs)

Koskari

#[*: xs 1 2]
#(1 *: xs)

Python

xs[0]
xs[-1]
d["a"]
xs[1:]

Koskari

xs[0]
xs[-1]
d["a"]
(item-slice xs 1)

Assignment

Python

x = 6
x = 7
x += 1
n -= 2

Koskari

var x 6
set! x 7
incr! x
decr! n 2

Why two words for =?

var introduces a name in the current scope. set! stores into a name that exists; inside a function, a set! to a name bound nowhere is a compile error that names the fix. Python lets one symbol do both and infers which you meant.

Python

obj.count = 0
d["k"] = v

Koskari

set! obj.count 0
set! d["k"] v

Conditionals

Python

if x < 0:
    s = "negative"
elif x == 0:
    s = "zero"
else:
    s = "positive"

Koskari

var s:
  if (< x 0):
    "negative"
  elif (== x 0):
    "zero"
  else:
    "positive"

Why does the var go outside?

An if is an expression. Its value is the value of the branch that ran, so one var takes the whole choice. Python’s if is a statement, so each branch assigns.

Python

def invite(user):
    if not user:
        return "missing user"
    if not user.email:
        return "missing email"
    return f"sent to {user.email}"

Koskari

def invite [user]:
  unless user:
    return "missing user"
  unless user.email:
    return "missing email"
  f"sent to {user.email}"

What are when and unless?

One-armed if. when runs its body if the test is true, unless if it is false. They keep a guard clause positive instead of writing (not ...).

Loops

Python

while n > 0:
    n -= 1
else:
    print("done")

Koskari

while (> n 0):
  decr! n
else:
  print "done"

Python

for item in seq:
    if item.ok:
        continue
    process(item)

Koskari

for-each [item seq]:
  when item.ok:
    continue
  (process item)

Does for-each have else and break?

Both, as in Python: the else: peer runs when the loop ends without a break. break may carry a value, which becomes the value of the loop:

var found:
  for-each [v values]:
    when (> v 2):
      break v
  else:
    None

Python

a and b
a or b
not a

Koskari

(and a b)
(or a b)
(not a)

Run it

Save as countdown.ki:

def countdown [n]:
  while (> n 0):
    print n
    decr! n
  else:
    print "lift-off"

@entry-point
def main []:
  (countdown 3)
3
2
1
lift-off