**Mu: making programs easier to understand in the large** Imagine a world where you can: 1. think of a tiny improvement to a program you use, clone its sources, orient yourself on its organization and make your tiny improvement, all in a single afternoon. 2. Record your program as it runs, and easily convert arbitrary logs of runs into reproducible automatic tests. 3. Answer arbitrary what-if questions about a codebase by trying out changes and seeing what tests fail, confident that *every* scenario previous authors have considered has been encoded as a test. 4. Run first simple and successively more complex versions to stage your learning. I think all these abilities might be strongly correlated; not only are they achievable with a few common concepts, but you can't easily attack one of them without also chasing after the others. The core mechanism enabling them all is recording manual tests right after the first time you perform them: * keyboard input * printing to screen * website layout * disk filling up * performance metrics * race conditions * fault tolerance * ... I hope to attain this world by creating a comprehensive library of fakes and hooks for the entire software stack, at all layers of abstraction (programming language, OS, standard libraries, application libraries). To reduce my workload and get to a proof-of-concept quickly, this is a very *alien* software stack. I've stolen ideas from lots of previous systems, but it's not like anything you're used to. The 'OS' will lack virtual memory, user accounts, any unprivileged mode, address space isolation, and many other features. To avoid building a compiler I'm going to do all my programming in (virtual machine) assembly. To keep assembly from getting too painful I'm going to pervasively use one trick: load-time directives to let me order code however I want, and to write boilerplate once and insert it in multiple places. If you're familiar with literate programming or aspect-oriented programming, these directives may seem vaguely familiar. If you're not, think of them as a richer interface for function inlining. Trading off notational convenience for tests may seem regressive, but I suspect high-level languages aren't particularly helpful in understanding large codebases. No matter how good a notation is, it can only let you see a tiny fraction of a large program at a time. Logs, on the other hand, can let you zoom out and take in an entire *run* at a glance, making them a superior unit of comprehension. If I'm right, it makes sense to prioritize the right *tactile* interface for working with and getting feedback on large programs before we invest in the *visual* tools for making them concise. **Taking mu for a spin** First install [Racket](http://racket-lang.org) (just for the initial prototype). Then: ```shell $ cd mu $ git clone http://github.com/arclanguage/anarki ``` As a sneak peek, here's how you compute factorial in mu: ```lisp function factorial [ ; create some space for the variables below default-scope:scope-address <- new scope:literal, 30:literal ; receive inputs in a queue n:integer <- next-input { ; if n=0 return 1 zero?:boolean <- equal n:integer, 0:literal break-unless zero?:boolean reply 1:literal } ; return n*factorial(n-1) tmp1:integer <- subtract n:integer, 1:literal tmp2:integer <- factorial tmp1:integer result:integer <- multiply n:integer, tmp2:integer reply result:integer ] ``` Programs are lists of instructions, each on a line, sometimes grouped with brackets. Each instruction operates on some *operands* and returns some *results*. ``` [results] <- instruction [operands] ``` Result and operand values have to be simple; you can't nest operations. But you can have any number of values. In particular you can have any number of results. For example, you can perform integer division as follows: ``` quotient:integer, remainder:integer <- divide-with-remainder 11:literal, 3:literal ``` Each value provides its data as well as its type separated by a colon. Types can be multiple words, like: ```lisp x:integer-array:3 ; x is an array of 3 integers y:list:integer ; y is a list of integers ``` In addition you can store other properties in values, separated by slashes. ```lisp x:integer-array:3/uninitialized y:string/tainted:yes z:list:integer/assign-once:true/assigned:false ``` These properties don't mean anything to mu, and it'll silently skip them when running, but they'll allow you to write *meta-programs* to check or modify your programs, a task other languages typically hide from their programmers. For example, where other programmers are restricted to the checks their type system permits and forces them to use, you'll learn to create new checks that make sense for your specific program. If it makes sense to perform different checks in different parts of your program, you'll be able to do that. To summarize: instructions have multiple operand and result values, values can have multiple rows separated by slashes, and rows can have multiple columns separated by colons. Only the very first column of the first row in each value's table is required to run mu programs, but the rest of the value table helps *manage* them over time. Management over time is why programming has traditionally been hard. Try out the factorial program now: ```shell $ ./mu factorial.mu result: 120 # factorial of 5 ... # ignore the memory dump for now ``` (The code in `factorial.mu` has a few more parentheses than the idealized syntax above. We'll drop them when we build a real parser.) --- An alternative way to define factorial is by including *labels*, and later inserting code at them. ```lisp function factorial [ default-scope:scope-address <- new scope:literal, 30:literal n:integer <- next-operand { base-case: } recursive-case: ] after base-case [ ; if n=0 return 1 zero?:boolean <- equal n:integer, 0:li