- I can't safely modify, or even understand, this code, because it has no tests.
- I can't test it without modifying it.
Check out this book on Goodreads: https://www.goodreads.com/book/show/44919.Working_Effectively_with_Legacy_Code

.hgrc
:[ui] username = Pozorvlak <pozorvlak@example.com> merge = internal:merge [pager] pager = LESS='FSRX' less [extensions] rebase = record = histedit = ~/usr/etc/hg/hg_histedit.py fetch = shelve = ~/usr/etc/hg/hgshelve.py pager = mq = color =
I've been running benchmarks again. The basic workflow is
Suppose I want to benchmark three different simulators with two different compilers for three iteration counts. That's 18 configurations. Now note that the problem found in stage 5 and fixed in stage 6 will probably not be unique to one configuration - if it affects the invocation of one of the compilers then I'll want to propagate that change to nine configurations, for instance. If it affects the benchmarks themselves or the benchmark-invocation harness, it will need to be propagated to all of them. Sounds like this is a job for version control, right? And, of course, I've been using version control to help me with this; immediately after step 1 I check everything into Git, and then use git fetch
and git merge
to move changes between repositories. But this is still unpleasantly tedious and manual. For my last paper, I was comparing two different simulators with three iteration counts, and I organised this into three checkouts (x1, x10, x100), each with two branches (simulator1
, simulator2
). If I discovered a problem affecting simulator1
, I'd fix it in, say, x1's simulator1
branch, then git pull
the change into x10 and x100. When I discovered a problem affecting every configuration, I checked out the root commit of x1, fixed the bug in a new branch, then git merge
d that branch with the simulator1
and simulator2
branches, then git pull
ed those merges into x10 and x100.
Keeping track of what I'd done and what I needed to do was frankly too cognitively demanding, and I was constantly bedevilled by the sense that there had to be a Better Way. I asked about this on Twitter, and Ganesh Sittampalam suggested "use Darcs" - and you know, I think he's right, Darcs' "bag of commuting patches" model is a better fit to what I'm trying to do than Git's "DAG of snapshots" model. The obvious way to handle this in Darcs would be to have six base repositories, called "everything", "x1", "x10", "x100", "simulator1" and "simulator2"; and six working repositories, called "simulator2_x1", "simulator2_x10", "simulator2_x100", "simulator2_x1", "simulator2_x10" and "simulator2_x100". Then set up update
scripts in each working repository, containing, for instance
and every time you fix a bug, run#!/bin/sh darcs pull ../base/everything darcs pull ../base/simulator1 darcs pull ../base/x10
for i in working/*; do $i/update; done
. But! It is extremely useful to be able to commit the output logs associated with a particular state of the build scripts, so you can say "wait, what went wrong when I used the -static
flag? Oh yeah, that". I don't think Darcs handles that very well - or at least, it's not easy to retrieve any particular state of a Darcs repo. Git is great for that, but whenever I think about duplicating the setup described above in Git my mind recoils in horror before I can think through the details. Perhaps it shouldn't - would this work? Is there a Better Way that I'm not seeing?
strace
or some equivalent.stat
is slow on modern filesystems.#include
.[Exercise for the reader: which build tools make which assumptions, and which compilers violate them?]
#!/usr/bin/perl print "> "; while (<>) { s|-?\d+(\.\d+)?([eE][-+]?\d+)?|10**($&/10)|oeg; print 10*log(eval $_)/log(10)."\n> "; }I've always thought of this as The Most Evil Code I've Ever Written. For those of you who don't speak fluent regex, it reads a line of input from the user, interprets everything that looks like a number as a number of decibels, replaces each decibel-number with the non-decibel equivalent, evaluates the resulting string as Perl code, and then converts the result back into decibels. Here's an example session:
> 1 + 1 4.01029995663982 > 10 * 10 20 > cos(1) -5.13088257108395Some of you are no doubt thinking "Well of course that code's evil, it's written in Perl!" But no. Here's the same algorithm written in Python:
#!/usr/bin/python -u import re, math, sys def repl(match): num = float(match.group(0)) return str(10**(num/10)) number = re.compile(r'-?\d+(\.\d+)?([eE][-+]?\d+)?') while 1: line = sys.stdin.readline() if len(line) == 0: break line = re.sub(number, repl, line) print 10*math.log10(eval(line))If anything, the Perl version is simpler and has lower accidental complexity. If Perl's not the best imaginable language for expressing that algorithm, it's damn close.
undefined reference to `__stginit_pluginszm1zi5zi1zi4_SystemziEvalziHaskell_'
errors, suggesting my installation of cabal
is b0rked. Anyone know what I need to do to fix it? Also, I'm sure my Python style can be greatly improved - suggestions most welcome.]eval
? And who the hell adds decibels, anyway?eval
to Aaron Crane the other day, and I mentioned this program. His response surprised me:I disagree; I think it’s a lovely piece of code. It may not be a beautiful jewel of elegant abstractions for a complex data model, true. But it’s small, simple, trivial to write, works on anything with a Perl interpreter (of pretty much any vintage, and with no additional dependencies), and clearly correct once you’ve thought about the nature of the arithmetic you’re doing. While it’s not something you’d ship as part of a safety-critical system, for example, I can’t see any way it could be realistically improved as an internal tool, aimed at users who are aware of its potential limitations.[Full disclosure: the Perl version above didn't work first time. But the bug was quickly found, and it did work the second time :-)]
Regexp::Common
from CPAN...", I'd have lost half my potential audience. As it was, the tool was enthusiastically adopted.perl -e 'print join q[{,-,+}], 1..9'and
perl -e 'print glob "1{,-,+}2"'at the command-line.
make
already has support for parallel builds, using the -j option.top
, I found that only one copy of the simulator or compiler was ever running at a time. What the hell? Time to look at the code: Oh God. Dear colleague, you appear to have taken a DSL explicitly designed to provide parallel tracking of dependencies, and then deliberately thrown that parallelism away. What were you thinking?¹ But it turns out that Dominus' Razor applies here, because getting the desired effect without sacrificing parallelism is actually ( remarkably hard... )TARGETS=build run collect clean distclean %-eembc2: eembc-2.0 @for dir in $(BMARKS_EEMBC2) ; do \ if test -d eembc-2.0/$$dir ; then \ ${MAKE} -C eembc-2.0/$$dir $* ; \ fi; \ done
make
, which means we're doing too much work if there's much code shared between different benchmarks. But since the time taken to run a benchmark is utterly dominated by simulator time, I'm not too worried about that."Branch over unconditional jump" hack for arbitrary-length brcc. - brcc (branch and compare) instructions can have static branch-prediction hints, but can only jump a limited distance. - Calculating the distance of a jump at expand-time is hard. - So instead of emitting just a brcc instruction, we emit an unconditional jump to the same place and a branch over it. - I added a simple counter to emit/state to number the labels thus introduced. - With this commit, I forfeit all right to criticise the hackiness of anyone else's code. - Though I doubt that will stop me.
ssh rydell ssh sentinel
(which sshes into rydell, and invokes the command ssh sentinel
thereon). This failed with the error "Pseudo-terminal will not be allocated because stdin is not a terminal". Marco Fontani pointed out that the -t switch to ssh allocates a pseudo-terminal, so ssh -t rydell ssh sentinel
Does What I Want.ForwardX11 yes
and Compression yes
to the relevant stanza of ~/.ssh/config
) I could enable compressed forwarding of X11 connections; I could then create new terminal windows by sshing into sentinel once and creating new xterms on there. I already had ForwardX11 set, but didn't know about Compression, so I've enabled that; it seems to help.ControlMaster auto ControlPath /tmp/ssh_mux_%h_%p_%r ServerAliveInterval 60 ServerAliveCountMax 60 Host rydell User pvlak1 HostName rydell.my.employ.er Host sentinel User pvlak1 ProxyCommand=ssh rydell nohup nc sentinel 22 HostName sentinel.my.employ.er Host svn.my.employ.er User pvlak1 ProxyCommand=ssh rydell nohup nc svn 22to
~/.ssh/config
. Then, at the beginning of the day, I set things up with the commands kinit pvlak1 autossh -f -M 0 -N sentinel autossh -f -M 0 -N svn.my.employ.erI can now open a new ssh connection to sentinel in an eyeblink. I needed to install AutoSSH, but this was just an apt-get away.
HostName
line is necessary to stop Kerberos getting confused. The first four lines were suggested by Marco Fontani and Aaron Crane, and allow ssh to multiplex all its connections to sentinel (or any host, come to that) over one channel, eliminating the need for a cryptographic handshake on each new connection and leading to blazingly-fast startup times. To avoid various annoying problems with this setup, you'll need the AutoSSH invocations: Aaron explains why on his blog.kinit
and then giving it my password, I should be able to log in to any machine on my employer's network (including sentinel) without typing my password again that day. Which is brilliant.pozorvlak@delight:~ 0 $ ssh rydell ssh sentinel Pseudo-terminal will not be allocated because stdin is not a terminal.
expect
#!/usr/bin/expect -f set timeout 30 match_max 100000 spawn ssh rydell send "\r" expect "0 " # prompt send "ssh sentinel\r" expect "0 " send "cde\r" # cd to work directory interactThis actually works, right until I open a text editor or other ncurses program, and discover that I can't resize my xterm - or rather, that the resize information is not passed on to my programs.
sshuttle
sshuttle
is a poor man's VPN written by the author of redo
. Using the -H option, it allows you to proxy your DNS requests through the remote server's DNS server. So a simplesshuttle -H -vvr rydell 0/0at the beginning of the day allows me to ssh directly from my local machine (delight) to sentinel. But! It asks me for my sodding password every single time I do so. This is not what I wanted.
pozorvlak@delight:~ 0 $ ssh -fN -L 9500:sentinel:22 rydell pozorvlak@delight:~ 0 $ ssh -p 9500 pvlak1@localhost pvlak1@localhost's password: Last login: Thu May 19 14:31:32 2011 from rydell.my.employ.er pvlak1@sentinel 14:34 ~ 0 $Yes, my employer is located in Eritrea, what of it? :-) Anyway, you will note that this suffers from the same problem as the previous attempt: I have to type my password for every login. Plus, if the sshuttle manpage is to be believed, tunnelling ssh over ssh is a bad idea performance-wise.
-t
option to ssh allocates a pseudo-terminal, so ssh -t rydell ssh sentinel
Does What I Want. Thanks, Marco! And thanks to everyone else who offered suggestions..ssh/config
now contains the stanzaHost sentinel User pvlak1 ProxyCommand=ssh rydell nohup nc sentinel 22 HostName sentinel.my.employ.erThis doesn't require me to type a password for every login, does allow me to resize ncurses apps, and feels slightly snappier than
ssh -t rydell ssh sentinel
, so that's what I'll be using from now on. Thanks very much!14. Rule of Generation: Avoid hand-hacking; write programs to write programs when you can.He goes into this idea in more detail in chapter 9 of the same book.
gen/
directory. switch
statement, which switches on some kind of type tag.OUTFILE
, and the output file isn't even open at the expansion stage. Clearly, the code had originally been written to run in the emit phase (when both state
and OUTFILE
were live), and he'd got half-way through converting it to run at expansion-time before having to abandon it.1 sperm has 37.5MB of DNA information in it. That means a normal ejaculation represents a data transfer of 1587GB in about 3 seconds.That's cute, but there's a more interesting question lurking here: it may represent 1.5TB of data, but how much information is transferred? In other words, how many bits would be required to convey the same "message" if we compressed it as cleverly as possible?
If your test fails, you'll get a compile-time error on that line. Aaaaaaaargh. I do wish someone had told me that before I spent a week mucking about with elisp. Well, that's not quite true - I learned a lot about Emacs, I had some fun writing the code, I learned some things about Agda that I might otherwise not have done, I got some code-review fromtest1 : 2 + 1 == 3 test1 = refl test2 : 3 + 0 == 3 test2 = refl
==
type constructor describes computable equality: a == b
is inhabited if the compiler can compute a proof that a
is equal to b
. "They both normalise to the same thing" is such a proof, and obviously one that the compiler's smart enough to look for on its own.agda-test
allows is balanced out by its inability to coerce both expressions to the same type, which often means the user has to type a lot more code. Stevena's approach does not suffer from this problem.ordarcs get http://patch-tag.com/r/pozorvlak/agda-test
Then load the filegit clone git://github.com/pozorvlak/agda-test.git
agda-test.el
in your .emacs
in the usual way. Having done so, you can add tests to your Agda file by adding comments of the form For instance,{- test TESTNAME: ACTUAL is EXPECTED; -}
When you then invoke the function{- test 2+1: (suc (suc zero)) +N (suc zero) is (suc (suc (suc zero))); test 3+0: (suc (suc (suc (zero)))) +N zero is (suc (suc zero)); -}
agda2-test-all
(via C-u C-c C-v
for "verify"), you should be presented with a new buffer called *Agda test results*
, containing the text [ Except, er, I get "expected _175" for that last test instead. I don't think that's a bug in my elisp code, because I get the same result when I evaluate that expression manually with C-c C-n. Halp pls?]1..2 ok 1 - 2+1 not ok 2 - 3+0 got 3 expected 2