mmm / bc1b4d6
alivecoding page s-ol 3 years ago
4 changed file(s) with 145 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
root/research/alivecoding/link: URL -> youtube$video less more
0 # alivecoding: <mmm-embed wrap="raw" facet="description"></mmm-embed>
1 peristant expressions are an approach to livecoding that unifies direct
2 manipulation of a dataflow engine with a textual representation and
3 lisp-based programming language.
5 <mmm-embed wrap="raw" path="demo"></mmm-embed>
7 ## shortcomings of repl-based programming
8 in repl-based environments, a scratch file is opened in a text editor. in it,
9 commands are staged and can be added, removed and edited without consequence.
10 the livecoding system generally has no knowledge about this scratch buffer at
11 all. the user is free to select and send individual commands (or groups of
12 commands) at any time and execute them by transmitting them to the server via
13 an editor plugin.
15 commands are incremental changes (deltas) that get sent to the server, which
16 keeps an entirely separate and invisible model of the project. generally no
17 feedback about the state of this model is made available to the user.
19 code is only executed when the user evaluates a block, although code run in
20 this fashion may cause other code to execute outside of the user-evaluated
21 execution flow via side effects, for example by registering a handler for
22 events such as incoming messages or scheduling execution based on system time.
23 these mechanisms however are implementation details within the code the user
24 executed originally, and no uniform mechanism for noticing, visualizing or
25 undoing these side-effects exists.
27 this design has the following consequences:
29 - the view of the scratch buffer is not correlated with the code and state the
30 server is currently executing. this results in overhead for keeping the
31 mental synchronized with what the system is actually performing for the user,
32 but also makes it much harder for the audience to follow along.
33 - sessions cannot be reopened reliably, because the state of the server depends
34 on the full sequence of commands that were sent to the server in order, which
35 is not represented in the scratch buffer.
36 - if parts of the execution model on the server have not been explicitly
37 labelled (i.e. assigned to a variable) in the textual representation, often
38 many potentially important actions for modifying the current behaviour are
39 unavailable: for example long-running sounds may not be cancellable, effects'
40 parameters may not be adjustable without recreating the signal chain, etc.
42 ## persistent expressions
43 the *persistent expression* paradigm, on the other hand, reconciles the user-
44 facing, text-based representation of the system and the server-internal model
45 and execution flow.
47 ### execution flow
48 code execution happens in two different phases alternatingly: at *eval-time*,
49 whenever the buffer is (re)evaluated; and at *run-time*, continuously between
50 evaluations.
52 at *eval-time*, execution is analogous to common functional and lisp-style
53 languages. expressions are evaluated depth-first starting from the root.
54 for each expression, the head of the expression is first evaluated, and
55 depending on the type of that subexpression different actions are taken. in the
56 general case, the head of an expression is an *op* (operator) type, an instance
57 of which will continue to run at *run-time*. in this case, all other arguments
58 are then evaluated and passed to the *op* instance, which is either created or
59 reused (see below).
60 on the other hand, some expressions (for example `def`, `use`, ...) do not
61 execute at *run-time*, but cause *eval-time* side-effects like declaring a
62 symbol in the active scope. because *eval-time* execution only happens once and
63 in a deterministic order, and no *eval-time* state persists across evaluations,
64 despite these side-effects, the *eval-time* execution is equivalent to
65 functionally pure execution with an implicit scope parameter.
67 unlike normal lisps, when evaluating expressions, not only a value is
68 generated. in parallel to the tree of return values, a tree of *run-time*
69 dependencies is built, that tracks all instantiated *op*s and their inputs.
71 at *run-time*, *op* instances update based on this dependency tree. starting
72 from a periodic root event polled by the interpreter, dependent *op*s are
73 executed (following the outside-in, depth-first order that the dependencies have
74 been created in at *eval-time*). *op*s whose inputs are unchanged and 'pure'
75 subtrees that do not have any dependency on the root event are not executed.
76 in this way, the *run-time* behaviour of the system is that of a event-driven
77 dataflow language with clearly defined execution flow.
79 ### expression tagging
80 in order to maintain the congruency between the representations across edits
81 and reevaluations, the identity of individual expressions is tracked using
82 tags. tags are noted using unique numbers in square brackets before the head of
83 expressions (e.g. `([1]head arg1 arg2...)`) and are optional when parsed.
85 at *eval-time* (see below), every expression that is not tagged will be
86 assigned a new unique tag number. 'cloned' expressions, such as the expressions
87 from a function definition body, are assigned composite tags that can be noted
88 as a list of tags joined by periods (e.g. `[2.1]`):
90 ```
91 ([1]defn add-two-and-multiply (a b)
92 ([2]mul b ([3]add a 2)))
94 ([4]add-two-and-multiply 1 2)
95 ([5]add-two-and-multiply 3 4)
96 ```
98 will be expanded (at *eval-time*) to approximately<span class="sidenote">
99 the actual implementation does not actually create sub expressions as shown
100 here, but the results behave equivalently.</span>:
102 ```
103 (do
104 (def a 1
105 b 2)
106 ([4.2]mul a ([4.3]add b 2)))
107 (do
108 (def a 3
109 b 4)
110 ([5.2]mul a ([5.3]add b 2)))
111 ```
113 the expression tags are used to associate the *run-time* representations (*op*
114 instances) of expressions with their textual representations, and track their
115 identity as the user changes the code. when the code is evaluated, *op*s are
116 instantiated whenever the expression was previously untagged, or when the head
117 of the expression no longer resolves to the same value. otherwise, the previous
118 *op* instance continues to exist and parameter changes are forward to it. *op*s
119 that are no longer referenced in the code are destroyed.
121 ### benefits
122 this approach combines the benefits of dataflow programming for livecoding with
123 those of a textual representation and the user-controlled evaluation moment.
125 dataflow:
127 - direct manipulation of individual parameters of a system without disturbing
128 the system at large
129 - execution and dataflow are aligned and evident in the editable representation
130 - state is isolated and compartmentalized in locally
131 - opportunity to visualize dataflow and local state<span class="sidenote">
132 visualizing state of individual *op*s in editor-dependent and editor-agnostic
133 ways that integrate with the textual representation is an ongoing research
134 direction of this project</span>
136 textual representation and user-controlled evaluation moment:
138 - high information density
139 - fast editing experience
140 - accessibility and editability from a wide range of tools (any text editor)
141 - ability to harness powerful meta-programming facilities (from Lisp)
142 - complex changes can be made without intermittently disrupting the system