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.

You should have read LANGUAGE.md first — this assumes 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 somewhere that runs things, and it can only be put there deliberately.


( ) 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

Being able to write it by hand is not a party trick. It is the reason a function 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 two 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 — reserved

/ambient is accepted and does nothing an undeclared name does not do. It is reserved for names a function expects to find wherever it is run. Writing it is harmless; it changes nothing.


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 machine has five registers, and they have short names because they are plumbing rather than vocabulary:

   
/_i the input — work waiting to be done
/_e the environment — what names mean here
/_s the stack
/_c the control
/_d the dump

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 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.

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