What a value says about itself
In osen a function is not a special kind of thing. It is a bundle with a couple of named parts, and the parts have names you can read. This page is about those names: what they are, how to write one by hand, and why the language works this way.
This assumes the tour: that you can already read
(1, 2) /o/add.
The one rule
A bundle can say which of the machine’s registers it carries. Running it means installing them. A bundle that says nothing is data.
Everything below is that sentence with examples under it.
The important half is the last clause. A bundle you were handed — read off a socket, pulled out of a message, sent from another machine — does not do anything by being looked at. It runs when something puts it where things run: the machine’s input, which is also where a host puts what it is sent (Between hosts says what that door checks).
( ) and { }
Both group things. The difference is when.
(1, 2) # → ( : 1, : 2 )
{1, 2}
The second prints : { : 1, : 2 }: the braces are still on. ( ) means
evaluate this here. At the top level “here” is immediately, so
(1, 2) has already run and left you a bundle. { } means don’t — you get
the thing itself, unevaluated, to keep or pass on.
That is why arguments go in parentheses and a function body goes in braces:
(1, 2) /o/add # → : 3
The parenthesised part is evaluated, then handed to /o/add. If you wrote
the body of a function in parentheses it would run at the moment you defined
it, which is not what you meant.
Defining a function
/o/defn takes a body in braces and the names the body is waiting for:
/g : ({ (/a, /b) /o/add }, ["/a","/b"]) /o/defn,
(1, 2) /g
# → : 3
A function is a value made of what you wrote, which is why one can arrive over a network: there is nothing in it that only this machine understands.
Naming the parts
/o/defn also takes its arguments by name, in any order:
/g : ( /body : { (/a, /b) /o/add },
/args : ["/a","/b"] ) /o/defn,
(1, 2) /g
# → : 3
The positional form is shorthand for the first two. The named form exists because there are more slots, and a positional list that grows breaks every reader of it.
/captured — fix a name where the function is written
By default a function looks names up where it runs:
/c : 10,
/g : ( /body : { /c } ) /o/defn,
/c : 99,
() /g
# → : 99
Declare the name and it is resolved and stored when the function is defined, so later changes do not reach inside it:
/c : 10,
/g : ( /body : { /c }, /captured : ["/c"] ) /o/defn,
/c : 99,
() /g
# → : 10
This is what makes a function portable: everything it needs travels with it.
An omitted list means empty, never “work it out” — so a function without
/captured captures nothing, and one that lists a name that is not bound
anywhere captures nothing and says nothing. Nothing is inferred on your
behalf.
/ambient — expect a name where the function runs
/ambient lists names a function expects to find where it runs. They are
read there, as an undeclared name is, so declaring one changes no answer:
/c : 10,
/g : ( /body : { /c }, /ambient : ["/c"] ) /o/defn,
/c : 99,
() /g
# → : 99
/text and /doc — what it was written as, and what it is for
Two strings a function can carry, neither required. /text is what it was
written as, and /doc a line about what it is for. They change nothing
about running it:
/f : ( /body : { (/x, 2) /o/mul }, /args : ["/x"], /doc : "twice its argument" ) /o/defn,
(21) /f
# → : 42
A program reads them as it reads any part of a value:
/f : ( /body : { 1 }, /doc : "one" ) /o/defn,
(/f, ("/doc")) /o/get
# → : "one"
So a function says what it is wherever it goes, and the text can’t come apart from the function: a function bound again in its place brings its own, or none. Each is one string; anything else is refused.
When a name is not bound
An undeclared name that resolves nowhere is not an error in the language. It is a question, and the host answers it. A command line reports it; a Max patch can send the name out an outlet and let the patch supply a value; a microcontroller host can ship the whole computation somewhere that does know the name.
So “not bound” is where a program asks, not only where it fails.
Finding out where a name came from
/foo : 7, ("/foo") /o/whence
# → : "/_e"
("/o/add") /o/whence
# → : "/_x"
("/nope") /o/whence
# → : ""
/_e is your environment, /_x the host register, a three-character
answer like /zz is a register, and "" means nowhere. It asks the same
question the machine asks when it resolves a name, so it cannot disagree with
it — and it never triggers the unbound behaviour above, so you can ask about
a name without setting anything in motion.
Implementation — for embedders and module authors
Implementation
What they turn into
A bundle can be an element of another bundle, or an item inside a message. Those are different places, and OSC only lets a bundle be the first one — so anything that lands in a message gets tagged to say what it is:
/a : (1,2), (/a) /o/typeof1 # → : "/-/|"
/b : {1,2}, (/b) /o/typeof1
# → : "/-/b"
| means “this message item holds a bundle, treat it as a sequence”; b is
an ordinary blob. Both are representations. Neither one means the value
will run. You will see | in printed output and it is worth recognising,
but you do not have to do anything about it.
The registers
The registers a value can declare have short names because they are
plumbing rather than vocabulary: the input /_i, the environment /_e,
the stack /_s, the control /_c and the dump /_d, with the parameter
list /_p below; the overview lists every
register.
A value that says /_i : <a bundle> is saying “this bundle is code, and
running me means making it the input.” That is all a function is.
A thunk
The smallest thing that says it is code:
/t : { /_i : { (1,2) /o/add } },
() /t
# → : 3
Read it outside in:
- the outer
{ }keeps the whole thing from running when you write it — it is the value you are holding; /_isays what it carries;- the inner
{ }is the body, and it has to be a bundle.
The inner braces are not decoration. Without them you have written an assignment whose value is an expression, not a declaration whose value is a body, and the call fails:
/t : { /_i : (1, 2) /o/add }, () /t
wrong type of item
Running one
Apply it with (), as above. /o/exec also works, on a literal or through
a name:
(( /_i : { (1,2) /o/add } )) /o/exec
# → ( : 3 )
/t : { /_i : { (1,2) /o/add } }, (/t) /o/exec
# → ( : 3 )
A bundle that declares nothing comes back untouched, which is the rule at the top doing its job:
/a : (1,2), (/a) /o/exec # → ( : ( : 1, : 2 ) )
/o/exec did not refuse and did not run it. There was nothing to install, so
you got your bundle back.
A hand-written function
Add parameters to a thunk and you have a function. /_p is the list of
names the body is waiting for:
/g : { /_i : { (/a, /b) /o/add }, /_p : ["/a","/b"] },
(1, 2) /g
# → : 3
That is a hand-written lambda, with no special syntax and no constructor.
It is an ordinary bundle with two ordinary addresses, and you can print it,
diff it, store it, or send it somewhere. /o/defn builds exactly the same
value. A hand-written one can carry /text and /doc too, and they are
left alone when it runs.
Note the two vocabularies: you write /body and /args, and the value
carries /_i and /_p. That is deliberate — the short names are the
machine’s registers, and you should not have to know them to write a
function.
Why it is built this way
The machine’s whole state is an OSC bundle, so a running program and a message on a wire are the same bytes. That is only useful if a receiver can be handed those bytes and decide what to do — and it can only decide if the bytes say what they are.
Hence the rule at the top. A value that declares /_i is offering to be run.
A value that declares nothing is data, and stays data no matter who receives
it or where they put it. There is no tag, no convention and no out-of-band
agreement that turns one into the other; you read the addresses, which are
ordinary OSC addresses that you could have written yourself.
See also
- The tour
- GRAMMAR.md — the syntax rules, precisely
- CALLING-CONVENTION.md — what happens to values at a call
- REFERENCE.md — every
/o/*name