
Mu: making programs easier to understand in the large - peterkelly
https://github.com/akkartik/mu
======
vinodkd
Things I really liked:

\+ recording runs as tests

\+ one library for all layers of abstraction

\+ building in assembler to avoid the big runtime.

\+ using literate programming techniques to manage coding in assembly.

\+ scenario, "screen-should-contain" and "assume-keyboard". awesome.

\+ spaces: nice primitive of the closure and similar concepts

\+ attributes for meta programs. again, awesome.

\+ labels and using [,] as labels. genius.

Suggestions:

* If there's a valid reason to call them recipes, go ahead. If they're actully just functions, please stick with what everyone knows and gets. Same thing with "reagents","reply", "ingredients" and "products". Update: I did read your blog post about this. still think the regular names are better in the long run.

* I dont know where you're going to with multiple types of the "number:list" kind, but if so, the map must look the same, not lispy.

In all, I really like where you're going with this. Kudos! I find a lot of
resonance of ideas I've had for a long time now.

~~~
akkartik
Wow, that's a very detailed look. Thanks! Feel free to email me; my address is
in my profile.

The terminology is definitely a work in progress. It's mostly for my attempts
to teach programming using Mu. I noticed that mathematical words intimidated
some students. I tried to write up my rationale here:
[http://akkartik.name/post/mu](http://akkartik.name/post/mu). But I've
certainly started mixing up the terms with my student, so it might not last.

~~~
slang800
> Functions, arguments, classes, methods, objects, threads, locks, all these
> are reassuring everyday words, and yet their meaning in programming (and
> math) bears no relation to their everyday meaning. (from
> [http://akkartik.name/post/mu](http://akkartik.name/post/mu))

I can see how the use of the term "arguments" could be confusing ("parameters"
or "inputs" would make more sense), and "threads" is a rather tenuous metaphor
for how scheduling works within a kernel, but all of the others mirror their
real-world meaning pretty well. I'd be more worried about students needing to
unlearn "containers", "ingredients", and "reagents" when they start reading
material from outside of Mu, talking to other developers, or learning calculus
(which uses functions, sets, and arrays).

~~~
akkartik
Actually, the only thread a ten-year-old knows is the one you might play cat's
cradle with. It isn't obvious why there should be anything exclusive about
one, or why you're better off keeping multiple of them 'separate'.

Functions are what something is for, as opposed to form. It isn't natural to
think about their inputs and outputs.

It's quite possible my solutions are too blunt and problematic, but these seem
like real problems.

~~~
nickpsecurity
Ada called them tasks. I always thought a multi- _tasking_ program made more
sense intuitively if one knew what a task was. A language designed for easy
learning might call them actions, activities, jobs a la 1960's... something
more intuitive.

Interesting, as I tried a quick brainstorm, I thought of a group of kids
sitting together playing with the same toys. Actions that only one could do on
a toy at the same time. Trying to convey the problems or exclusivity. Mentally
came to problem of sharing. Then that people often borrowed toys temporarily
then the owner checked up on them.

(lightbulb) Rust has a borrow-checker. And ownership. Now I wonder where they
came up with that haha.

~~~
slang800
I'm not sure if a term like "tasks" / "jobs" / "actions" makes more sense. You
wouldn't necessarily separate each thing that a program does into a new
thread... Functions are for separating each body of work, while threads
represent a path of execution through a process. In this way multiple lines of
execution are intertwined to form a process, just as real threads are
intertwined to form a rope... It's not a very good metaphor, but I can't think
of a better one.

In Ada, "tasks" makes sense, but only because it is an abstraction that makes
use of threads while managing all memory access and separation from the rest
of the process.

~~~
nickpsecurity
Hmm. Good points. Maybe make an analogy to sonething like traffic lanes? More
lanes equals more throughput. Not always utilized and diminishing gains as you
add lanes for given workload. Problems occur during intersections where they
share a resource. Requires synchronization, signalling, and/or ordering
protocols to maintain safety.

How you like THAT! Maybe need a similar analogy closer to whats going on but
the spirit of it seems accurate. Maybe factory workers on assembly lines or
offics workers at desks.

~~~
akkartik
I like that very much.

------
enqk
So I wrote an implementation of linear search in Mu using its array type.

[https://gist.github.com/uucidl/df81b6ab0fe65713f5ba](https://gist.github.com/uucidl/df81b6ab0fe65713f5ba)

My notes:

\- creating an array w/ create-array is possible, however I could not find how
to take its address and pass it around

\- one cannot get the address of an invalid position of the array, so one
cannot construct bounded ranges (STL style) using a begin/end pair of
addresses

\- the interpreter strongly suggests to use refcounted pointers
(address:shared) instead of addresses even when one does not borrow the memory

\- could not figure out how to write a test scenario that uses checks named
memory addresses rather than ordinal memory addresses (memory-should-contain
instruction)

~~~
akkartik
That's great!! Yes, _create-array_ was just a version I created so I could
teach arrays separate from _new_ and addresses. But it creates an array on the
'stack' (default space) so there's no way to take an address of it. I quickly
take my students past it to _new_.

 _memory-should-contain_ doesn't currently support named locations, sorry.
Part of the problem is that with spaces a name can have many different
addresses in different functions. So my tests write stuff I want to check in
raw numbered locations, and the first 1000 addresses are reserved for tests so
that names can never be clobbered.

I looked at Rust's borrow checker for a bit but wasn't smart enough to
understand how it works or transplant it easily. I also noticed that it wasn't
smart enough to deal with things like doubly-linked lists without reaching for
ref-counting. So I figured I'd keep things simple and just use ref-counting
for everything. That way I punt on all the complicated static checks in favor
of a simple runtime one.

The rule is: _new_ returns _shared:address_ , and _get-address_ and _index-
address_ (and _maybe-convert_ ) return _address_. Use _shared:address_ to pass
things around between functions, and reserve non-shared addresses only for
short-term operations, usually mutations. Since non-shared addresses are not
dynamically allocated, there's no possibility of use-after-free so they don't
need to be refcounted, and you can copy them around as much as you like.

Use-after-free and related memory corruption is really the only thing I'm
concerned about protecting my users from. Memory leaks I plan to have tools
for, so that programmers can identify and break them down when memory becomes
a concern, but not worry about until then. I call this "zero-developer-cost
abstractions" :) It feels less restrictive and more dynamic than Rust.

 _Edit_ : I just took a look at your code, and it feels perfectly idiomatic.
Nice job. Only issue I found was that you forgot to specify the outputs of
_find_ in its header. So its calls end up doing some runtime type-checking. I
should probably raise a warning in this situation. Thanks again.

~~~
enqk
Indeed, I missed the output for find! A warning would definitely be helpful.

I like that you have users and thus will be able to test things out and get
some genuine feedback.

This might however introduce a bias guiding your language and standard library
in a certain direction (I think it's inevitable and what's happening in all
languages anyway) .. Therefore I would add some of your peers into the mix.

Otherwise I really like the directions you are exploring, as I've come to very
similar conclusions at this point in my career: \- being able to modify a
system through safe additions \- not focusing too much on local details of a
system

~~~
akkartik
Yeah, the reactions here have been invaluable. It's interesting: I actually
got into teaching because I had trouble getting programmers to try Mu out.
Most people tend to unconsciously filter out what seems gratuitously new and
different. But I'll keep trying.

------
sja
I'm a little hesitant to learn how to 'understand large programs', or how to
structure them for that matter, from a repository that has 100 code files in
the root directory.

I'm not sure what I'm looking at?

~~~
akkartik
Author here. For my part, I have never understood why people consider it to be
a good thing to squirrel code into a bunch of different sub-directories.

a) It makes the build scripts more complicated, which means they'll be more
likely to break on some poor noob, and that when they break it'll be less
likely the noob will be able to tell how to fix it.

b) Invariably the codebase accumulates dependencies between directories that
are uneconomic to reorganize. At least a flat directory has a shot at under-
promising and over-delivering.

c) Maybe people do it to make the place look neat, they way I used to 'clean
my room' by stuffing all my dirty clothes into drawers. But codebases that are
messes at a deep level are less likely to be cleaned up if they look clean at
a superficial level. And _all_ codebases eventually turn into messes, the way
we've done things so far.

~~~
i573323
That same line of thinking could be used to just put everything into a single
huge file.

~~~
nickpsecurity
It's not the same. I can open, process, or skim a directory containing a
thousand files without much trouble or CPU activity. Having a thousand files
worth of information in one file means I have to run through all that data at
once. This has implications in data integrity as well if I'm making changes or
worried about bitrot.

So, no, there's a difference. I'll also note that Daniel Bernstein of qmail &
NaCl often used a filesystem how some use databases under the philosophy of
"Why create new, complex functionality when you have well-tested code that
gets the job done?"

------
dman
I think the project would benefit from a Terminology / Definitions section
which lists in one table the names and crisp definitions of the concepts being
introduced. It appears that you are using non standard terms so having a
single list of the terms would allow readers to have that list open when they
are reading through the layers.

~~~
akkartik
That's a good idea. Might
[http://akkartik.github.io/mu/html/010vm.cc.html](http://akkartik.github.io/mu/html/010vm.cc.html)
[1] be such a big-picture map? Let me think about how to improve it; I haven't
touched it in a while. Further suggestions most welcome.

[1] Colorized version of
[https://github.com/akkartik/mu/blob/379244666c/010vm.cc](https://github.com/akkartik/mu/blob/379244666c/010vm.cc)

~~~
dman
I was thinking something much simpler. If you were writing a book about Mu
what would be the definitions you would print on the back inner cover? A table
with two columns - first column being a term and the second column containing
its description in one or two sentences.

Could you elaborate on the target audience for the doc you linked to? I am
unable to make out whether that doc is intended for serious programmers in
other languages coming across Mu for the first time or whether its intended
for new comers to programming.

~~~
akkartik
Yeah the intended audience is existing C++ programmers coming across Mu for
the first time. For teaching programming I mostly imagine I'll be doing it in
person. Mu isn't yet ready for learning programming unassisted.

------
desireco42
Definitely could use better presentation. It is interesting, reminds me of
forth a little, where you compose a lot of small commands.

------
vmorgulis
The preprocessor reminds me a bit MyDef:

[https://github.com/hzhou/MyDef](https://github.com/hzhou/MyDef)

~~~
akkartik
Indeed! I had a stimulating conversation with Hui Zhou last year.

------
antiquark
How is "subtract a, 3" better than "a-3"?

I can already feel my carpal tunnel acting up!

~~~
akkartik
It translates to assembly/machine code with a _lot_ less code. I don't need a
compiler, I don't need to write optimizations.[1]

I actually don't consider Mu to be a language. (I'm the author.) It's a low-
level _starting point_ to explore ways to make the standard OS primitives more
testable. I don't need it to look nice. Higher level languages can come later,
once the foundations are in place.

I actually am growing increasingly suspicious of all our navel-gazing about
syntax. We spend all our time in places like HN thinking about how to make
small screenfuls of code look nice, but all that effort hasn't really
translated into helping me understand the large-scale structure of random
open-source codebases more easily.

Perhaps one way to think about it is that syntax helps insiders keep a
codebase in their head. I'm more concerned with ways to help more outsiders
import a codebase in the first place. Because people leave and move on, and
it's very hard for software projects today to improve once their original
authors move on. More on this: [http://akkartik.name/post/readable-
bad](http://akkartik.name/post/readable-bad)

(I'm susceptible to carpal tunnel, but it hasn't become a bigger issue. I take
wrist breaks.)

[1] I can even imagine doing the translation in machine code, so that Mu then
becomes self-hosting without needing any of the current stack. I might go down
that road..

~~~
userbinator
_I don 't need a compiler, I don't need to write optimizations._

You've effectively created a sort of portable assembly language, but it's more
verbose than usual assembly language ("sub eax, 3").

 _but all that effort hasn 't really translated into helping me understand the
large-scale structure of random open-source codebases more easily._

I think the root of the problem is that software is being written to be more
complex than it could/should be, so the solution is to encourage reducing
complexity.

~~~
akkartik
_You 've effectively created a sort of portable assembly language_

Exactly. The verbosity is mostly because of my teaching project. I think
teaching assembly can be just as ergonomic as teaching a high-level language.
Indeed, most of us from a generation ago learned programming using assembly.
It just needs to get a little more ergonomic.

 _the solution is to encourage reducing complexity._

Yup. The problem is that human beings have a _terrible_ track record at
managing complexity. It's not just every software project ever; think about
the creep of bureaucracy in ancient China, or the creep of legislation in
ancient Rome. Everytime we've created a repository of rules it's gotten
complex (and then gamed by smart operators). I think the problem is that it's
very hard to justify removing a rule, so such repositories grow monotonically.
The only way to periodically prune unnecessary rules is to first track why you
created them in the first place
([https://en.wikipedia.org/wiki/Wikipedia:Chesterton's_fence](https://en.wikipedia.org/wiki/Wikipedia:Chesterton's_fence)).
Hence: tests! I think they are the great advance bequeathed to the human race
by software. And they're far more broadly applicable outside software --
though I have no idea how to do this applying. I wrote up some speculative
ideas about it at [http://www.ribbonfarm.com/2014/04/09/the-legibility-
tradeoff](http://www.ribbonfarm.com/2014/04/09/the-legibility-tradeoff) a
couple of years ago, but that piece isn't very clear.

~~~
dman
While working on large code bases I have often (in half jest) wished for the
concept of a runtime performance tax on code that is engineered poorly. Some
variants of this could be a. Imposing an exponentially increasing programmatic
sleep on functions based on their length. b. Repeated database / filesystem /
network requests for identical resources multiple times in a program should
again incur a slowdown.

------
kazinator
> _Imagine a world where you can:_

> _[...] 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._

If we alter that to "three programs you use [...] all in a single afternoon",
we can plonk that into my resume.

