osen

Everything you write in osen is a message. A value is a message, a program is a bundle of messages, and a function you define is a bundle stored under a name. The messages are OpenSoundControl (OSC), and that is not a slogan about the implementation: because a program is OSC, it can be sent somewhere and run there — which is the reason the language exists.

This page is the guided tour. The grammar is the precise syntax when you want the rules rather than the introduction.

Every example below was run before it was written down. To follow along:

cd hosts/unix.o.se && make && ./o.se -f o.se.osen/osen.ose

That is the path inside the asoma workspace; in a standalone clone of o.se the directory is o.se.


Values

Type a value and you get it back.

42
# → : 42
"hello"
# → : "hello"
3.14
# → : 3.14

The colon is part of the answer, and it is worth understanding immediately rather than learning to ignore.

In OSC, every message has an address and some data. A message can have an empty address, and that is what a bare value is: no name, just data. The colon marks where the name would go. So : 42 reads as “no name, value 42.”

Names

Put an address on the left of the colon and the message has a name.

/x : 10

Names are OSC addresses, so they start with / and can have several parts — /synth/1/freq is one name, not three. Use the name to get the value back:

/x : 10, /x
# → : 10

Note the comma. A comma separates the elements of a program. A newline is just whitespace, so this is the same program:

/x : 10,
/x

Leave the comma out and you do not get two statements — you get one, and it means something else entirely:

/x : 10
/x
# → /x is not bound

Without the separator, /x sits to the right of a value, and to the right of a value a name is a function call. So this asks to call /x on 10, and there is no function by that name.

That single rule causes more confusion than anything else in the language. When something behaves oddly, check the commas first.

Binding a name again replaces it:

/x : 10, /x : 20, /x
# → : 20

Calling things

The arguments come first, then the function.

(10, 3) /o/sub
# → : 7
("hello") /o/println
# → : "hello"

/o/println prints its argument and then hands it back as its answer, so at the prompt you see the string twice.

Read it as “take this bundle of arguments, and apply this to it.” If you have met a stack language or a shell pipeline, it is the same instinct: the data is already there and the operation comes after it.

Arguments can be names:

/x : 10, /y : 20, (/x, /y) /o/add
# → : 30

There are well over a hundred functions built in — arithmetic, comparison, string and blob operations, printing. /o/add, /o/sub, /o/mul, /o/concat/strings, /o/println are the ones you will reach for first.

Two kinds of bundle

Parentheses and curly braces both group things. The difference is when.

(1, 2)
# → ( : 1, : 2 )

That is a bundle, evaluated now: its two values are the answer.

{1, 2}

That is a bundle kept for later, and it comes back as a single value with the braces still on: : { : 1, : 2 }. Parentheses are how you pass arguments, as above. Curly braces are how you hold on to code without running it — which is what you need to define a function.

Choosing

/o/if takes three things: what to do if the test fails, what to do if it succeeds, and the test itself.

({"no"}, {"yes"}, 1) /o/if
# → : "yes"
({"no"}, {"yes"}, 0) /o/if
# → : "no"

The false branch comes first because ( ) evaluates its elements in order, and the test is the thing you want computed last, nearest the call. Only the chosen branch runs: the other is in curly braces, so it was never evaluated.

The result is an ordinary value, so it composes like one:

(({1}, {2}, 1) /o/if, 10) /o/mul
# → : 20

Anything other than zero is true. A string is true when it reads as a non-zero number, so "1" is true and "hello" is not.

One element in, one element out

Every element of a program evaluates to exactly one element. This rule has no exceptions, and everything below is one rule seen at different sizes: whatever an element produces is gathered into a single message.

Several values arrive together, as one message with several items:

({0}, {1, 2, 3}, 1) /o/if
# → : [ 1, 2, 3 ]
({0}, {1, 2}, 1) /o/if
# → : [ 1, 2 ]

One value is a message with one item, which is what a plain value already is:

({0}, {1}, 1) /o/if
# → : 1

And no values at all is a message with no items:

({0}, { }, 1) /o/if
# → :

That last row is the one worth pausing on, because “an empty branch does nothing” makes it sound as though nothing should come back. Something does, and it is the same value [] writes — a message with no items:

([], ({0}, { }, 1) /o/if) /o/eql
# → : 1

Two empty messages are equal, and an empty one is not equal to anything else:

([], []) /o/eql
# → : 1
([], [1]) /o/eql
# → : 0

Read the four rows together and there is nothing special about the last one. Gather N results into one message; N may be 0.

Why this is a rule and not a detail. It is what makes a call readable: (EXPR, 10) /o/mul passes two arguments no matter how much work EXPR does, so you never have to read inside a function to know how many arguments a call site has. An element that produced nothing would break exactly that — it would silently make the call one argument short, and every argument after it would land in the wrong parameter. Nothing checks for this, so it would not be an error, it would be a wrong answer.

When you want a bundle instead of a list, ask for one. Parentheses inside the branch build a bundle, and a bundle is a single value:

({0}, {(1, 2)}, 1) /o/if
# → ( : 1, : 2 )

That is a bundle of two messages, not a list.

Lists

Square brackets write several values into one message, which is the same shape a call with several results has:

[1, 2, 3]
# → : [ 1, 2, 3 ]

The empty list is a message with no name and no items, and it is a value like any other — you can write it directly in a call:

[]
# → :
([], 5) /o/push
# → : 5

Counting

/o/range turns a count into a sequence. The end is exclusive, so (n) gives exactly n numbers:

(5) /o/range
# → : [ 0, 1, 2, 3, 4 ]
(2, 6) /o/range
# → : [ 2, 3, 4, 5 ]
(0, 10, 3) /o/range
# → : [ 0, 3, 6, 9 ]

The step may be negative, and a range that cannot be walked is empty rather than an error:

(10, 0, -3) /o/range
# → : [ 10, 7, 4, 1 ]

Folding

/o/fold walks a sequence, carrying an accumulator. The step function is called as (acc, item) and what it returns becomes the next accumulator:

((5) /o/range, 0, /o/add) /o/fold
# → : 10
([1, 2, 3], 100, /o/add) /o/fold
# → : 106

An empty sequence gives back the initial value, untouched:

((0) /o/range, 42, /o/add) /o/fold
# → : 42

Items are visited left to right, and the step function can be anything — including one that decides:

/keep : ({ ( { /a }, { (/a, /x) /o/add }, (/x, 3) /o/lt ) /o/if },
         ["/a", "/x"]) /o/defn,
((6) /o/range, 0, /keep) /o/fold
# → : 3

fold and range are the two things that have to be built in, because an accumulator must survive an iteration and nothing else in the language carries state across one. Everything else — map, filter, each, any, all, find — is a few lines of osen written over these two.

What counts as a sequence

Everything above folds a list — one message, and its items are the sequence. A bundle is a sequence too, and its elements are:

((1, 2, 3), 10, /o/add) /o/fold
# → : 16
([1, 2, 3], 10, /o/add) /o/fold
# → : 16

One rule covers both: take the element you were handed and iterate its contents — the items of a message, the elements of a bundle. It is the same rule /o/zipwith uses for each of the sequences it zips, and every function built over fold inherits it.

Curly braces are not a sequence. { 1, 2, 3 } is a blob — the bundle deferred and encoded, this language’s thunk — sitting as the single item of a message. So it folds as a one-item sequence whose item is opaque, and arithmetic refuses it:

({ 1, 2, 3 }, 10, /o/add) /o/fold

That raises: the fold reaches the one item, a blob, and /o/add refuses it as the wrong type of item. The prompt says so in a sentence; a box shows the error and what was on the stack.

If you have a deferred bundle and you want its elements, make it an element: <{ 1, 2, 3 }> folds to 16, exactly as ( 1, 2, 3 ) does.

An empty sequence of either kind gives back the initial value:

((), 42, /o/add) /o/fold
# → : 42

Defining a function

/o/defn takes the body and the parameter names:

/double : ({(2, /n) /o/mul}, "/n") /o/defn,
(21) /double
# → : 42

The body is in curly braces because it must not run when /o/defn runs — it runs later, when /double is called. "/n" names the parameter, which the body refers to as /n.

/o/defn also takes its arguments by name, which is how you reach the other two slots it has:

/double : ( /body : {(2, /n) /o/mul}, /args : "/n" ) /o/defn,
(21) /double
# → : 42

A function is an ordinary bundle with named parts, and you can write one by hand without /o/defn at all. DECLARATIONS.md is that story: what the parts are called, how to fix a name at definition time so a function carries its own meaning, and why a bundle you were handed never runs by itself.

Another, with a string:

/greet : ({("hi ", /who) /o/concat/strings}, "/who") /o/defn,
("bob") /greet
# → : "hi bob"

A function is a value like any other. It is a bundle, stored under a name.

Where names live

A name is looked up in the environment that is live when it runs, not the one it was written in. That single sentence is the whole rule, and everything below is a consequence of it.

Binding a name inside a function binds it for that call. It disappears when the call returns:

/x : 40,
/g : ({ /x : 2 }, []) /o/defn,
(() /g, (/x, 1) /o/add)
# → ( : 2, : 41 )

The bundle holds both answers: 2 from the call to /g, the value it bound, and 41 from the add. /g set /x to 2 while it ran; afterwards /x is 40 again. The binding was put in front of the old one and taken away again, not written over it.

But while /g is running, anything it calls sees its /x.

/x : 40,
/f : ({ (/x, 1) /o/add }, []) /o/defn,
/g : ({ /x : 2, () /f }, []) /o/defn,
() /g
# → : 3

/f was written where /x was 40. Called from /g, it reads /g’s /x and answers 3. Call it from somewhere that binds nothing and it answers 41:

/x : 40,
/f : ({ (/x, 1) /o/add }, []) /o/defn,
/h : ({ () /f }, []) /o/defn,
() /h
# → : 41

So a function’s free names are not fixed when you write it. They are resolved every time it runs, against whoever is calling. If you have used Scheme, ML, Python or JavaScript, this is the part that will surprise you: those languages fix a function’s free names at the point of definition. In osen they are not — by default.

You can ask for the other behaviour one name at a time, and a function that does carries its meaning with it wherever it goes:

/x : 40,
/f : ( /body : { (/x, 1) /o/add }, /captured : ["/x"] ) /o/defn,
/g : ( /body : { /x : 2, () /f } ) /o/defn,
() /g
# → : 41

Same /f as above, and /g still binds /x : 2 around the call — but /f declared /x, so it kept the 40 it was written with and answers 41 rather than 3. DECLARATIONS.md has the rest of that story.

The three consequences

A function’s parameters are visible to what it calls. A parameter is an ordinary binding made for the duration of the call, so it behaves exactly like /x above:

/inner : ({ (/n, 1) /o/add }, []) /o/defn,
/outer : ({ () /inner }, "/n") /o/defn,
(41) /outer
# → : 42

/inner has no /n of its own and picks up /outer’s parameter.

A function returned from another function loses what it was written next to. There is nothing carried along with it:

/mk : ({ /n : 5, ({ /n }, []) /o/defn }, []) /o/defn,
/h : () /mk,
() /h
# → /n is not bound

Called inside /mk, that same inner function answers 5. Returned, it has no /n to find.

A name may be bound after the function that uses it. Because lookup happens at call time, a function can refer to something defined later, and redefining a function changes it for everything that calls it, immediately. That is deliberate: it is what makes editing a running patch work.

Practical advice

The order names are searched in

For a name like /xy/rest, in order: the register /xy if there is one, then the environment, then /_x. A miss at one step falls through to the next. Full detail, including why the first step exists, is in the registers.

The part worth knowing here: the environment is searched before /_x, so a name you bind replaces a standard-library name of the same address for as long as your binding is live. /o/add : 999 makes /o/add answer 999. The real one is untouched and still reachable as /_x/o/add.

Calling: what goes on the left, and what goes on the right

A call is two things side by side. The right-hand side is the function and the left is its arguments — which is the opposite way round from most languages, and is the one thing to learn here.

(1, 2) /o/add
# → : 3
/g : ({ (/a, 1) /o/add }, ["/a"]) /o/defn, (5) /g
# → : 6

The right-hand side does not have to be a NAME. It can be any group that produces a function, which means a lambda can be applied where it stands, and a function that returns a function can be called at once:

(5) (({ (/x, 1) /o/add }, ["/x"]) /o/defn)
# → : 6
/mk : ({ ({ (/x, 100) /o/add }, ["/x"]) /o/defn }, []) /o/defn, (5) (() /mk)
# → : 105

The left-hand side can be anything at all — a group, a thunk, a list, a number, a string. These all print their left side:

(3) /o/abs
# → : 3

What is NOT a call

The grammar is narrow on purpose, and the two rules are worth stating because everything outside them is refused rather than guessed at.

The right-hand side must be an ADDRESS or a BRACKET. (1) 2 and (1) "b" are parse errors — a literal or a string on the right is not a call. A string in particular never applies: an address is a NAME and a string is DATA, and making a string apply would mean a value applies depending on whether it happens to look like an address. ("/a") /o/select has to be able to pass /a as data.

A bracket on the right needs a bracket on the left. 1 (2), "a" (2) and /x (2) parse, and are then refused with “That parsed, but it is not a program” — two things next to each other with nothing joining them. Only (…) (…) and {…} (…) are calls.

And a right-hand side that is not a function is left alone rather than being an error. (1) (2) answers [ ( : 1 ), 2 ]: the two values, side by side, uncalled. That is a corner with no meaning assigned to it yet.

%{ } — a bundle that closes over the names it was written with

A { } is a quotation: nothing in it is evaluated where you write it, so a name in it is looked up wherever it eventually RUNS.

%{ } is the same bracket with one difference: its free names are filled in where it was written. That is what lets you build a program HERE and send it THERE with the values from here already in it.

The examples below run the quotation and read one name out of the result, so /x is deliberately rebound to 99 in between — the answer tells you which binding won.

/x : 7, /q : {  /a : /x }, /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : 99
/x : 7, /q : %{ /a : /x }, /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : 7

/%/ marks the exception, whichever bracket it is in

Inside a { } it closes ONE name. Inside a %{ } it leaves one OPEN. It is the exception in both directions, not a second way of saying “closed”.

/x : 7, /q : {  /a : /%/x }, /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : 7
/y : 9, /q : %{ /b : /%/y }, /y : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/b")) /o/get
# → : 99

A /%/ reaches any depth, because there is one construction and it fills every hole in it:

/x : 7, /q : { /a : { /b : { /c : /%/x } } }, /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a", "/b", "/c")) /o/get
# → : 7

Nesting: a bare { } inherits, a nested %{ } DEFERS

A plain { } inside a %{ } is not a construction of its own, so it is closed along with everything else. A %{ } inside anything IS one, and it happens when the bundle around it runs — not here.

/y : 9, /q : %{ /in : {  /b : /y } }, /y : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/in", "/b")) /o/get
# → : 9
/y : 9, /q : {  /in : %{ /b : /y } }, /y : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/in", "/b")) /o/get
# → : 99

Deferring is absolute rather than relative to the bracket around it — a nested %{ } waits even when what encloses it is itself closed:

/y : 9, /q : %{ /in : %{ /b : /y } }, /y : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/in", "/b")) /o/get
# → : 99

And a nested one does not drag its enclosure closed. The outer /a here is still filled where it runs:

/x : 7, /q : { /a : /x, /in : %{ /b : 1 } }, /x : 77, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : 77

Three places a value can come from

Which is the whole point of the two brackets and the marker: a program can be filled where it is written, where it runs, or left open for somewhere else again.

/s : 1, /q : %{ /led : /s },            /s : 2, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/led")) /o/get
# → : 1
/s : 1, /q : {  /led : /s },            /s : 2, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/led")) /o/get
# → : 2

The third is a /%/ inside a nested %{ }: the outer quotation runs, the inner construction runs with it, and the hole is still a hole afterwards — for whichever machine fills it next.

Names bound inside the bundle are not closed over

%{ } closes the names it READS from outside, not the ones it BINDS. A binding’s own right-hand side still sees the outer value, because it is read before the binding exists.

/t : 5, /q : %{ /t : 1, /u : /t },           ((/q /!/toelem/fromblob) /!/o/exec, (0, "/u")) /o/get
# → : 1
/u : 9, /q : %{ /u : (/u, 1) /o/add },       ((/q /!/toelem/fromblob) /!/o/exec, (0, "/u")) /o/get
# → : 10
/t : 5, /q : %{ /in : { /t : 1 }, /out : /t }, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/out")) /o/get
# → : 5

It works over items too

/x : 7, /q : {  /a : [1, /%/x, 3] }, /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : [ 1, 7, 3 ]
/x : 7, /q : %{ /a : [1, /x, 3] },   /x : 99, ((/q /!/toelem/fromblob) /!/o/exec, (0, "/a")) /o/get
# → : [ 1, 7, 3 ]

Recipes

Every example here is run by tools/check_docs.py on each make check, so none of them can quietly stop working.

Comparing. lt, lte, gt, gte are strict about what they say: lt is <, not <=.

(3, 5) /o/lt
# → : 1
(4, 4) /o/gte
# → : 1
(5, 3) /o/gt
# → : 1

Arithmetic that would be awkward with if.

(3, 7) /o/min
# → : 3
(-5) /o/abs
# → : 5
(0) /o/not
# → : 1

Applying a function to every item. That is /o/map:

([-1, 2, -3], /o/abs) /o/map
# → : [ 1, 2, 3 ]

The function can be one of your own:

([1, 5, 3], ({ (/x, 2) /o/mul }, "/x") /o/defn) /o/map
# → : [ 2, 10, 6 ]

Working across two lists at once. /o/zipwith applies a function to the i-th item of every sequence it is given, so it needs a bundle of sequences — which is what the extra <{ … }> is:

(<{ [1,2,3], [10,20,30] }>, /o/add) /o/zipwith
# → : [ 11, 22, 33 ]

Give it one sequence and it is a map, the long way round:

(<{ [-1, 2, -3] }>, /o/abs) /o/zipwith
# → : [ 1, 2, 3 ]

The sequences can be bundles rather than lists, and they can arrive through names — which is how they usually arrive, since a lambda parameter is a name. All four of these are the same zip:

(((1, 2), (3, 4)), /o/add) /o/zipwith
# → : [ 4, 6 ]
(<{ (1, 2), (3, 4) }>, /o/add) /o/zipwith
# → : [ 4, 6 ]
/s : ((1, 2), (3, 4)), (/s, /o/add) /o/zipwith
# → : [ 4, 6 ]
/a : (1,2), /b : (3,4), ((/a, /b), /o/add) /o/zipwith
# → : [ 4, 6 ]

The reason the last two work is worth knowing, because it is the same reason everywhere: a bundle does not survive being named. It becomes a MESSAGE whose one item is the bundle, and anything that wants to iterate it has to see through that. zipwith is the only function that has to do it twice — once for the bundle it is handed, and once for each sequence inside it.

That extra wrapper is the difference between them, and it is the easiest thing in the language to get wrong. map, fold, each, filter, any, all and find are all unary — you hand them a sequence. zipwith is the only n-ary one — you hand it a bundle of sequences. So the same list is written [1,2,3] for the first group and <{ [1,2,3] }> for zipwith, and dropping or adding the wrapper does not fail, it iterates the wrong thing:

([1, 2, 3], /o/count/items) /o/map
# → : [ 1, 1, 1 ]
(<{ [1, 2, 3] }>, /o/count/items) /o/map
# → : 3

The first walked three items and counted each. The second was handed a bundle holding one list, walked its single element, and counted that — a loop that ran once instead of three times, with no error to tell you so.

Reducing a list to one value. Pick the step function and the starting value and fold does the rest.

([3, 9, 4], 0, /o/max) /o/fold
# → : 9
([3, 9, 4], 999, /o/min) /o/fold
# → : 3

“Is every one of them true?” is a fold with and, and “is any one of them?” is the same fold with or:

([1, 0, 1], 1, /o/and) /o/fold
# → : 0
([0, 0, 1], 0, /o/or) /o/fold
# → : 1

Counting is a fold whose step ignores the item:

/count : ({ (/a, 1) /o/add }, ["/a", "/x"]) /o/defn,
((7) /o/range, 0, /count) /o/fold
# → : 7

Picking things out of a list. filter, any, all, find and each are written in osen over fold – they are in o.se.osen/higher.osen, four lines each, and you can add to them without touching C.

/gt2 : ({ (/x, 2) /o/gt }, "/x") /o/defn,
((6) /o/range, /gt2) /o/filter
# → : [ 3, 4, 5 ]
/gt2 : ({ (/x, 2) /o/gt }, "/x") /o/defn,
([1, 5, 2, 9], /gt2) /o/find
# → : 5
/gt2 : ({ (/x, 2) /o/gt }, "/x") /o/defn,
([1, 5, 2], /gt2) /o/any
# → : 1
/gt2 : ({ (/x, 2) /o/gt }, "/x") /o/defn,
([3, 1, 9], /gt2) /o/all
# → : 0

each runs a function for its effect and returns 0 rather than a list, so it cannot be mistaken for zipwith.

Strings.

(("a", "b") /o/concat/strings, "!") /o/concat/strings
# → : "ab!"
((7) /o/tostring, " items") /o/concat/strings
# → : "7 items"

Where this goes

Because a program is an OSC bundle, the thing you just wrote is data, and data can travel. osepy/host/examples/drive_audio.py builds an FM synth in a browser by writing osen in Python and sending it over a socket; the browser has no synth code in it, only a VM. The same program runs at the command line, in Max, or on a microcontroller.

That is the point of the language being OSC rather than merely producing it.

Next