mu/Readme.md

466 lines
17 KiB
Markdown
Raw Normal View History

Mu explores ways to turn arbitrary manual tests into reproducible automated
tests. Hoped-for benefits:
1. Projects release with confidence without requiring manual QA or causing
regressions for their users.
1. Open source projects become easier for outsiders to comprehend, since they
can more confidently try out changes with the knowledge that they'll get
rapid feedback if they break something.
1. It becomes easier to teach programming by emphasizing tests far earlier
than we do today.
In this quest, Mu is currently experimenting with the following mechanisms:
1. New, testable interfaces for the operating system. For example, printing to
screen explicitly takes a screen object, so it can be called on the real
screen, or on a fake inside tests, so that we can then check the expected
state of the screen at the end of a test. We're building up similarly
*dependency-injected* interfaces to the keyboard, mouse, touch screen,
disk, network, …
1. Support for testing side-effects like performance, deadlock-freedom,
race-freeness, memory usage, etc. Mu's *white-box tests* can check not just
the results of a function call, but also the presence or absence of
specific events in the log of its progress. For example, if a sort function
logs each swap, a performance test can ensure that the number of swaps
doesn't double when the size of the input doubles. Besides expanding the
scope of tests, this ability also allows more radical refactoring without
needing to modify tests. All Mu's tests call a top-level function rather
than individual sub-systems directly. As a result the way the subsystems
are invoked can be radically changed (interface changes, making synchronous
functions asynchronous, …). As long as the new versions emit the
same implementation-independent events in the logs, the tests will continue
to pass.
1. Organizing code and tests in layers of functionality, so that outsiders can
build simple and successively more complex versions of a project, gradually
enabling more peripheral features. Think of it as a cleaned-up `git log`
for the project. ([More information.](http://akkartik.name/post/wart-layers))
Since I don't understand how Linux and other modern platforms work, Mu is
built on an idealized VM while I learn. Eventually the plan is to transplant
what I learn back to Linux.
To minimize my workload, Mu doesn't have a high-level language yet. Instead,
I've been programming directly in the VM's idealized assembly language. I
expected this to be painful, but it's had some surprising benefits. First,
programs as lists of instructions seem to be easier for non-programmers to
comprehend than programs as trees of expressions. Second, I've found that
Literate Programming using layers makes assembly much more ergonomic. Third,
labels for gotos turn out to be great waypoints to insert code at from future
layers; when I tried to divide C programs into layers, I sometimes had to
split statements in two so I could insert code between them. They also seem a
promising representation for providing advanced mechanisms like continuations
and lisp-like macros.
High level languages today seem to provide three kinds of benefits:
expressiveness (e.g. nested expressions, classes), safety (e.g. type checking)
and automation (e.g. garbage collection). An idealized assembly gives up some
expressiveness benefits, but doesn't seem to affect the other categories.
2016-03-08 04:51:40 +00:00
2016-03-07 07:59:59 +00:00
*Taking Mu for a spin*
2015-06-10 17:22:02 +00:00
Mu is currently implemented in C++ and requires a unix-like environment. It's
2015-09-02 19:29:39 +00:00
been tested on ubuntu 14.04 on x86, x86\_64 and ARMv7 with recent versions of
gcc and clang. Since it uses no recent language features and has no exotic
dependencies, it should work with most reasonable versions, compilers or
processors.
Running Mu will always recompile it if necessary:
2015-06-10 17:22:02 +00:00
2015-08-10 23:14:38 +00:00
```shell
$ cd mu
2015-07-05 17:35:26 +00:00
$ ./mu
2015-08-10 23:14:38 +00:00
```
2015-11-11 17:13:40 +00:00
As a sneak peek, here's how you perform some simple arithmetic:
2015-11-11 17:13:40 +00:00
```nim
def example1 [
2015-11-11 17:13:40 +00:00
a:number <- add 2, 2
a <- multiply a, 3
]
```
But it's easier to read in color:
<img alt='code example' src='html/example1.png'>
Mu functions are lists of instructions, one to a line. Each instruction
operates on some *ingredients* and returns some *products*.
2015-08-10 23:14:38 +00:00
```
[products] <- instruction [ingredients]
2015-08-10 23:14:38 +00:00
```
2015-05-11 19:00:50 +00:00
Result and ingredient *reagents* have to be variables. But you can have any
number of them. In particular you can have any number of products. For example,
2015-05-11 19:00:50 +00:00
you can perform integer division as follows:
2014-11-26 16:30:26 +00:00
2015-08-10 23:14:38 +00:00
```
quotient:number, remainder:number <- divide-with-remainder 11, 3
2015-08-10 23:14:38 +00:00
```
2014-11-26 16:30:26 +00:00
2015-07-29 22:55:05 +00:00
Each reagent can provide a name as well as its type separated by a colon. You
only have to specify the type the first time you mention a name, but you can
2015-11-11 17:13:40 +00:00
be more explicit if you choose. Types can be multiple words and even arbitrary
trees, like:
2015-08-10 23:14:38 +00:00
```nim
x:array:number:3 # x is an array of 3 numbers
y:list:number # y is a list of numbers
2015-11-11 17:13:40 +00:00
# without syntactic sugar
{z: (map (address array character) (list number))} # map from string to list of numbers
2015-08-10 23:14:38 +00:00
```
2014-12-14 21:21:32 +00:00
2015-11-11 17:13:40 +00:00
Try out the program now:
```shell
$ ./mu example1.mu
$
```
Not much to see yet, since it doesn't print anything. To print the result, try
adding the instruction `$print a` to the function.
2015-11-11 17:13:40 +00:00
---
Here's a second example, of a function that can take ingredients:
2015-11-11 17:13:40 +00:00
<img alt='fahrenheit to celsius' src='html/f2c-1.png'>
2015-11-11 17:13:40 +00:00
2016-03-08 19:50:33 +00:00
Functions can specify headers showing their expected ingredients and products,
2016-03-08 08:15:04 +00:00
separated by `->` (unlike the `<-` in *calls*).
2015-11-11 17:13:40 +00:00
2016-03-08 08:15:04 +00:00
Since Mu is a low-level VM language, it provides extra control at the cost of
2015-11-15 00:26:33 +00:00
verbosity. Using `local-scope`, you have explicit control over stack frames to
isolate your functions (in a type-safe manner; more on that below). One
2015-11-15 00:26:33 +00:00
consequence: you have to explicitly `load-ingredients` after you set up the
stack.
2015-11-11 17:13:40 +00:00
An alternative syntax is what the above example is converted to internally:
<img alt='fahrenheit to celsius desugared' src='html/f2c-2.png'>
2015-11-11 17:13:40 +00:00
The header gets dropped after checking types at call-sites, and after
replacing `load-ingredients` with explicit instructions to load each
ingredient separately, and to explicitly return products to the caller. After
this translation functions are once again just lists of instructions.
2015-11-11 17:13:40 +00:00
This alternative syntax isn't just an implementation detail. I've actually
found it easier to teach functions to non-programmers by starting with this
2015-11-13 03:42:35 +00:00
syntax, so that they can visualize a pipe from caller to callee, and see the
names of variables gradually get translated through the pipe.
2015-11-11 17:13:40 +00:00
---
A third example, this time illustrating conditionals:
<img alt='factorial example' src='html/factorial.png'>
2015-11-11 17:13:40 +00:00
In spite of how it looks, this is still just a list of instructions.
Internally, the instructions `break` and `loop` get converted to `jump`
instructions to after the enclosing `}` or `{`, respectively.
2015-01-03 02:57:49 +00:00
Try out the factorial program now:
2015-08-10 23:14:38 +00:00
```shell
2015-01-03 02:57:49 +00:00
$ ./mu factorial.mu
result: 120 # factorial of 5
2015-08-10 23:14:38 +00:00
```
2015-01-03 02:57:49 +00:00
You can also run its unit tests:
2015-08-10 23:14:38 +00:00
```shell
$ ./mu test factorial.mu
2015-08-10 23:14:38 +00:00
```
Here's what one of the tests inside `factorial.mu` looks like:
<img alt='test example' src='html/factorial-test.png'>
Every test conceptually spins up a really lightweight virtual machine, so you
can do things like check the value of specific locations in memory. You can
also print to screen and check that the screen contains what you expect at the
end of a test. For example, `chessboard.mu` checks the initial position of a
game of chess (delimiting the edges of the screen with periods):
<img alt='screen test' src='html/chessboard-test.png'>
Similarly you can fake the keyboard to pretend someone typed something:
2015-08-10 23:14:38 +00:00
```
assume-keyboard [a2-a4]
2015-08-10 23:14:38 +00:00
```
As we add a file system, graphics, audio, network support and so on, we'll
augment scenarios with corresponding abilities to use them inside tests.
2015-01-03 02:57:49 +00:00
---
The name of a reagent is for humans, but what the computer needs to access it is
2015-01-03 02:57:49 +00:00
its address. Mu maps names to addresses for you like in other languages, but
2015-01-03 03:04:15 +00:00
in a more transparent, lightweight, hackable manner. This instruction:
2015-01-03 02:57:49 +00:00
2015-08-10 23:14:38 +00:00
```nim
z:number <- add x:number, y:number
2015-08-10 23:14:38 +00:00
```
2015-01-03 02:57:49 +00:00
might turn into this:
2015-08-10 23:14:38 +00:00
```nim
3:number <- add 1:number, 2:number
2015-08-10 23:14:38 +00:00
```
2015-01-03 02:57:49 +00:00
You shouldn't rely on the specific address Mu chooses for a variable, but it
2015-01-03 02:57:49 +00:00
will be unique (other variables won't clobber it) and consistent (all mentions
of the name will map to the same address inside a function).
2015-01-03 02:57:49 +00:00
Things get more complicated when your functions call other functions. Mu
doesn't preserve uniqueness of addresses across functions, so you need to
organize your names into spaces. At the start of each function (like
2015-03-20 03:50:00 +00:00
`factorial` above), set its *default space*:
2015-01-03 02:57:49 +00:00
```nim
2015-08-10 23:14:38 +00:00
local-scope
```
or
```nim
new-default-space
2015-01-03 02:57:49 +00:00
```
2015-08-10 23:14:38 +00:00
or
```nim
default-space:address:array:location <- new location:type, 30/capacity
```
Without one of these lines, all variables in the function will be *global*,
2015-08-10 23:14:38 +00:00
something you rarely want. (Luckily, this is also the sort of mistake that
will be easily caught by tests.) *With* this line, all addresses in your
function will by default refer to one of the (30, in the final case) slots
2015-08-10 23:14:38 +00:00
inside this local space. (If you choose the last, most explicit option and
2016-03-08 08:15:04 +00:00
need more than 30 slots, Mu will complain asking you to increase capacity.)
2015-01-03 02:57:49 +00:00
Spaces can do more than just implement local variables. You can string them
together, pass them around, return them from functions, share them between
parallel routines, and much else. However, any function receiving a space has
to know the names and types of variables in it, so any instruction should
always receive spaces created by the same function, no matter how many times
it's run. (If you're familiar with lexical scope, this constraint is
identical to it.)
To string two spaces together, write one into slot 0 of the other. This
instruction chains a space received from its caller:
2015-08-10 23:14:38 +00:00
```nim
0:address:array:location <- next-ingredient
2015-08-10 23:14:38 +00:00
```
2015-01-03 02:57:49 +00:00
Once you've chained spaces together, you can access variables in them by
adding a 'space' property:
2015-01-03 02:57:49 +00:00
2015-08-10 23:14:38 +00:00
```nim
3:number/space:1
2015-08-10 23:14:38 +00:00
```
2015-01-03 02:57:49 +00:00
2015-08-10 23:14:38 +00:00
This reagent is the number in slot 3 of the space chained in slot 0 of the
2015-01-03 02:57:49 +00:00
default space. We usually call it slot 3 in the 'next space'. `/space:2` would
be the next space of the next space, and so on.
See `counters.mu` for an example of managing multiple accumulators at once
without allowing them to clobber each other. This is a classic example of the
sorts of things closures and objects are useful for in other languages. Spaces
in Mu provide the same functionality.
2015-01-03 02:57:49 +00:00
---
You can append arbitrary properties to reagents besides types and spaces. Just
2015-01-03 02:57:49 +00:00
separate them with slashes.
2014-12-14 21:21:32 +00:00
2015-08-10 23:14:38 +00:00
```nim
x:array:number:3/uninitialized
2014-12-14 21:21:32 +00:00
y:string/tainted:yes
z:list:number/assign-once:true/assigned:false
2015-08-10 23:14:38 +00:00
```
2014-12-14 21:21:32 +00:00
Most properties are meaningless to Mu, and it'll silently skip them when
2015-01-03 02:57:49 +00:00
running, but they are fodder for *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
2014-12-14 21:21:32 +00:00
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.
2015-08-13 15:27:18 +00:00
You can imagine each reagent as a table, rows separated by slashes, columns
within a row separated by colons. So the last example above would become
something like this:
2015-01-08 03:34:23 +00:00
2015-08-10 23:14:38 +00:00
```
2015-08-13 15:27:18 +00:00
z : list : integer /
assign-once : true /
2015-01-08 03:34:23 +00:00
assigned : false
2015-08-10 23:14:38 +00:00
```
---
2015-01-03 02:57:49 +00:00
An alternative way to define factorial is by inserting *labels* and later
2014-11-26 16:30:26 +00:00
inserting code at them.
2016-03-09 00:50:22 +00:00
<img alt='literate programming' src='html/tangle.png'>
2014-11-26 16:30:26 +00:00
(You'll find this version in `tangle.mu`.)
2015-05-11 19:00:50 +00:00
Any instruction without ingredients or products that starts with a
non-alphanumeric character is a label. By convention we use '+' to indicate
function-local label names you can jump to, and surround in '<>' global label
2016-02-13 07:55:45 +00:00
names for inserting code at.
2015-02-02 20:19:47 +00:00
2014-11-26 16:30:26 +00:00
---
2014-11-01 23:34:33 +00:00
Another example, this time with concurrency.
2016-03-09 00:50:22 +00:00
<img alt='forking concurrent routines' src='html/fork.png'>
2015-05-11 16:59:29 +00:00
2015-08-10 23:14:38 +00:00
```shell
$ ./mu fork.mu
2015-08-10 23:14:38 +00:00
```
2014-11-01 23:34:33 +00:00
Notice that it repeatedly prints either '34' or '35' at random. Hit ctrl-c to
stop.
2014-11-26 16:48:06 +00:00
Yet another example forks two 'routines' that communicate over a channel:
2014-11-25 09:25:20 +00:00
2015-08-10 23:14:38 +00:00
```shell
$ ./mu channel.mu
2014-11-25 09:25:20 +00:00
produce: 0
produce: 1
produce: 2
produce: 3
consume: 0
consume: 1
consume: 2
produce: 4
consume: 3
consume: 4
# The exact order above might shift over time, but you'll never see a number
# consumed before it's produced.
2015-08-10 23:14:38 +00:00
```
2014-11-25 09:25:20 +00:00
Channels are the unit of synchronization in Mu. Blocking on channels are the
2014-11-25 09:25:20 +00:00
only way tasks can sleep waiting for results. The plan is to do all I/O over
channels that wait for data to return.
Routines are expected to communicate purely by message passing, though nothing
stops them from sharing memory since all routines share a common address
space. However, idiomatic Mu will make it hard to accidentally read or clobber
2014-11-25 09:25:20 +00:00
random memory locations. Bounds checking is baked deeply into the semantics,
and pointer arithmetic will be mostly forbidden (except inside the memory
allocator and a few other places).
---
2015-05-16 06:01:37 +00:00
If you're still reading, here are some more things to check out:
a) Look at the [chessboard program](http://akkartik.github.io/mu/html/chessboard.mu.html)
for a more complex example where I write tests showing blocking reads from the
keyboard and what gets printed to the screen -- things we don't typically
associate with automated tests.
b) Try skimming the [colorized source code](http://akkartik.github.io/mu). I'd
like it to eventually be possible to get a pretty good sense for how things
work just by skimming the files in order, skimming the top of each file and
2015-08-13 15:27:18 +00:00
ignoring details lower down. Tell me how successful my efforts are.
2015-05-16 06:01:37 +00:00
c) Try running the tests:
2015-08-10 23:14:38 +00:00
```shell
$ ./mu test
2015-08-10 23:14:38 +00:00
```
2014-11-26 16:30:26 +00:00
You might also want to peek in the `.traces` directory, which automatically
includes logs for each test showing you just how it ran on my machine. If Mu
eventually gets complex enough that you have trouble running examples, these
logs might help figure out if my system is somehow different from yours or if
I've just been insufficiently diligent and my documentation is out of date.
2015-07-30 21:16:30 +00:00
d) Try out the programming environment:
2015-08-10 23:14:38 +00:00
```shell
$ ./mu test edit # takes about 30s; shouldn't show any failures
$ ./mu edit
2015-08-10 23:14:38 +00:00
```
2015-07-30 21:16:30 +00:00
Screenshot:
<img alt='programming environment' src='html/edit.png'>
2015-07-30 21:16:30 +00:00
You write functions on the left and try them out in *sandboxes* on the right.
2015-09-06 23:52:48 +00:00
Hit F4 to rerun all sandboxes with the latest version of the code. More
details: http://akkartik.name/post/mu. Beware, it won't save your edits by
default. But if you create a sub-directory called `lesson/` under `mu/` it
2015-07-30 21:16:30 +00:00
will. If you turn that directory into a git repo with `git init`, it will also
back up each version you try out.
2015-08-13 15:27:18 +00:00
Once you have a sandbox you can click on its result to mark it as expected:
<img alt='expected result' src='html/expected-result.png'>
2015-08-13 15:27:18 +00:00
Later if the result changes it'll be flagged in red to draw your attention to
it. Thus, manually tested sandboxes become reproducible automated tests.
<img alt='unexpected result' src='html/unexpected-result.png'>
2015-08-13 15:27:18 +00:00
Another feature: Clicking on the code in a sandbox expands its trace for you
2015-08-13 15:58:41 +00:00
to browse. To add to the trace, use `stash`. For example:
```nim
stash [first ingredient is ], x
```
Invaluable for understanding complex control flow without cluttering up the
screen.
2015-08-13 15:27:18 +00:00
2016-02-29 17:48:40 +00:00
The next major milestone on Mu's roadmap is dependency-injected interfaces for
the network and file system.
2015-08-13 15:27:18 +00:00
2014-11-29 15:52:08 +00:00
**Credits**
Mu builds on many ideas that have come before, especially:
2014-11-26 15:04:04 +00:00
- [Peter Naur](http://alistair.cockburn.us/ASD+book+extract%3A+%22Naur,+Ehn,+Musashi%22)
for articulating the paramount problem of programming: communicating a
codebase to others;
2014-11-26 15:04:04 +00:00
- [Christopher Alexander](http://www.amazon.com/Notes-Synthesis-Form-Harvard-Paperbacks/dp/0674627512)
and [Richard Gabriel](http://dreamsongs.net/Files/PatternsOfSoftware.pdf) for
the intellectual tools for reasoning about the higher order design of a
codebase;
- Unix and C for showing us how to co-evolve language and OS, and for teaching
the (much maligned, misunderstood and underestimated) value of concise
*implementation* in addition to a clean interface;
2014-11-26 15:04:04 +00:00
- Donald Knuth's [literate programming](http://www.literateprogramming.com/knuthweb.pdf)
for liberating "code for humans to read" from the tyranny of compiler order;
2014-11-26 15:04:04 +00:00
- [David Parnas](http://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf)
and others for highlighting the value of separating concerns and stepwise
refinement;
2014-11-26 15:04:04 +00:00
- [Lisp](http://www.paulgraham.com/rootsoflisp.html) for showing the power of
2015-06-24 22:46:30 +00:00
dynamic languages, late binding and providing the right primitives *a la
carte*, especially lisp macros;
- The folklore of debugging by print and the trace facility in many lisp
systems;
- Automated tests for showing the value of developing programs inside an
elaborate harness;
2014-11-26 15:04:04 +00:00
- [Python doctest](http://docs.python.org/2/library/doctest.html) for
exemplifying interactive documentation that doubles as tests;
2014-11-26 15:04:04 +00:00
- [ReStructuredText](https://en.wikipedia.org/wiki/ReStructuredText)
and [its antecedents](https://en.wikipedia.org/wiki/Setext) for showing that
markup can be clean;
- BDD for challenging us all to write tests at a higher level;
- JavaScript and CSS for demonstrating the power of a DOM for complex
structured documents.