I/O and effects

An effect is what a form does besides producing a value: printing, reading a file, storing into a name, raising an error. Koskari’s effects are Python’s, reached through the same objects.

Printing

print is Python’s print:

print "hello" "Koskari"
print "no newline" end: ""
print "to stderr" file: sys.stderr

Keyword arguments pass as name: value. At the REPL, a form’s value is shown without print; in a file, only print writes to the terminal.

Files and context managers

with runs a body inside a context manager and binds its enter value:

with [f (open path)]:
  print (f.read)

The bracket holds the name and the expression. With no name to bind, write with [(lock)]:. Cleanup runs when the body exits, by any route. An async context manager works the same way; dyn-with is the form for a manager that must be entered on the dynamic extent rather than the lexical one, which matters for continuations and rarely otherwise.

Exceptions

Errors are Python exceptions. raise raises one, and bare raise re-raises the current one:

raise (ValueError "count must be non-negative")
raise

try handles them. except arms name a class and optionally bind the instance; else: runs when nothing was raised; finally: always runs:

try:
  (load-config path)
except [FileNotFoundError as: e]:
  print "missing config" path
else:
  print "loaded"
finally:
  print "finished config attempt"

The arm [e FileNotFoundError] is the same binding in the older order, and a bare except: catches everything.

try is an expression. Its value is the body’s value, or the handling arm’s when one ran:

var port:
  try:
    (int text)
  except [ValueError]:
    8080

The parenthesized spelling puts the arms after the body:

(try (int text)
  [[e ValueError] 8080]
  [finally: (print "done")])

Raise for a case the caller cannot be expected to handle by checking first. Catch close to where recovery is possible. A missing name, a failing import, and a bad argument are all ordinary Python exceptions with the classes Python gives them; the Koskari-specific classes, for the read and compile stages and for the evaluator, are in the Error guide.

Stores

set!, incr!, and friends are effects on bindings, and a store into an attribute or a subscript is an effect on an object:

set! obj.count 0
incr! counts[word]

Bindings and refs covers what a store does.

Async effects

A call that returns an awaitable is awaited where it is called, so I/O through an async library reads the same as through a sync one:

def body [url]:
  (client.get url)     ;; awaited here if it suspends

The explicit await is for a value you chose to hold: a defer, a Task, a Future. The rules, and the demand path where suspension is not allowed, are in Demand, async, and defer.