start of vty 4 postmortem

Ignore-this: e2fffce4f0ab1cfcc1db121c8659ecdf

darcs-hash:20091007040028-f0a0d-f0edb1508665da5e04ca340a45affd4f75af42bc.gz
This commit is contained in:
coreyoconnor 2009-10-06 21:00:28 -07:00
parent cae018f8a9
commit fe55cd4dd4

View File

@ -0,0 +1,89 @@
The first project I focused on when I became fun-employed was improving Vty: A terminal output
library for Haskell software.
<lj-cut/>
Oh I know what you're thinking... Well no, but *I* think it's rediculous to spend time on a
*terminal* library. Something like OpenGL or web related, hell anything where any significant activity
has happened in the past few years seems more reasonable. Oh well.
This is a diary of completing vty 4 mostly written after I was done. No really specific technical
stuff is contained in this post. I suppose it counts as a sort of postmortem.
I had taken over as maintainer of vty 3 from Stefan O'Rear. Vty 3 already worked great and I didn't
really see me doing much. Still there is always something to improve. In this case vty did not
support the various terminals people wanted to use. And characters that occupy multiple output
columns were causing corruption.
Plus, optimization is always fun. So trying my hands at optimizing Haskell code sounded great.
Especially when, for the most part, there was an already fast and already working version to compare
performance data against: Vty 3.
I figured low level optimization was what I should start on. Which only makes sense considering I
was only interested in optimization fun at the time. ;-) The result of this were much faster than
before. However, since I wasn't changing the design in any significant fashion some optimizations
could not be implemented. In the end this route was only useful to define reasonable performance
goals for a rewritter output layer.
In addition I was learning Mandarin at the time. I wanted to, of course, create software to help me
study. Since I have an infatuation with terminal user interfaces I wanted a terminal library that
could handle double-width characters. I couldn't see an effective way to implement this with vty 3's
implementation.
Anybody who has needed to reimplement, or deal with the horrors of a botch reimplementation, knows
how dangerous this can be. A botched reimplementation ends up costing more than continueing to
maintain the old implementation. A reimplementation is only feasable if the immediate cost of
performing the work is offset by the profits the improvements provide.
A particular source of trouble for vty was the insanity of dealing with terminals or terminal
emulators. I'm never going to refer to the physical box of relays from the 70s and 80s that is
properly called a terminal again. Terminal emulators are now be refered to as terminals and the
others don't exist. So...
Terminals are software driven character displays and a keyboard. The software controls the diplay by
serializing to the STDOUT UTF-8 byte character sequences. Which are then displayed. And control
codes which modify the display of the characters. Input from the keyboard and events are read from
STDIN.
Why the fuck something as old as a terminal hasn't been beaten down into a simple, universally
supported set of operations by now is a mystery. I don't think curses are terminfo count. I suspect
if support for everything that cannot support the required interface is dropped things would only be
better. For this reason I only focused on supporting the following terminals: xterm-256-color with
UTF-8; Mac OS X Terminal.app; gnome terminal, kde terminal; and rxvt-unicode. Basically: All the
terminals I could easily use and behaved close to how I expected.
While I did not consider supporting the Windows platform I hoped that the abstractions used to
handle the various non-Windows terminals would simplify supporting Windows in the future.
To assure the re-implementation did not introduce regressions I repeatedly:
0. Characterized vty 3's implementation. Both in terms of functionality and performance.
1. Defined the semantics for the new implementation.
2. Verified the new implementation performed as expected: The semantics defined in 1 were
implemented correctly; No characteristics of vty 3's implementation that should be maintained
are missing; Verified characteristics of vty 3 that caused issues were not maintained.
Not all the verification steps could be automated. Some I didn't know how to. Others were
just verified through informal analysis.
The verification of some features was done by implementing an interactive test that guided and
recorded the results of a manual review. For instance the libraries representation of red and what
is actually required to get a terminal to display red. The only reasonable way to verify that final
map was for me to sit there and look at the output. Then record whether or not the output was as
expected. Since the same tests were going to have to be performed repeatedly and I wanted to record
the results of the tests I formalized this process in software: tests/interactive_terminal_test.hs.
This program recorded the results of: Describing a test to a user; Performing the test; Requesting
from the user if the test passed or failed; Then recording the users response. This paid for itself
very quickly. Not only did provide the framework to easily create about 15 individual tests. But
the program could also worked as a sort of bug reporting tool for users.
The verification that could be entirely automated was done either through the type system or
<a href="http://en.wikipedia.org/wiki/QuickCheck">QuickCheck 2</a> based verification tests.
In short an loose terms: QuickCheck informally verifies equations satisfy user specified predicates
for arbitrarially generated input. Not all input is attempted; that'd take too long. However enough
is tried to be reasonably sure that an implementation works. The looseness of the verification is
made up for the fact that QuickCheck tests are *extremely* easy and quick to implement.
I used a very simple Makefile to manage the execution of tests. The usage followed:
make => built and ran all tests.
make TEST => build and run test with name TEST. The output for a test was logged to a "results"
directory.
Nothing fancy, but enough.