Over the last few weeks I've had a couple of sessions of working on a
library to wrap Plotext -- a popular
terminal-based plotting library for Python -- so that it can easily be used
in Textual apps;
textual-plotext
is the result.
I feel it's come together pretty well
But... I've been itching to find a reason to use it in a project of my own.
Meanwhile...
Back in the mid-2000s, when phpBB
systems were still the fashion, I used to hang out on a site that was
chiefly aimed at the atheist and secular humanist crowd. We'd get a good
number of drive-by
YEC types who'd
want to argue (sorry... debate) and often talk nonsense about biology and
the like.
Now, I'm no biologist, I'm no scientist, I'm just a hacker who likes to
write code for fun and profit; so any time there was a chance to write some
code to help illustrate an idea I'd jump at the chance. I forget the detail
now -- this was back in 2008; 15 years ago as of the time of writing -- but
one time I remember a conversation was taking place where someone was just
flat out claiming that "random mutation" can only cause "loss of
information" and could never lead to a "desired result", or some such thing.
If you've ever had, read or watched those debates, you'll know the sort of
thing I mean.
So that got me thinking back then, could I write something that could give a
simple illustration of how this doesn't quite make sense?
So I had a little hacking session and came up with some Ruby code that
did what I felt was the job. You'd give it a phrase you wanted it to
generate (a stand-in for the current "fitness landscape", in effect), it
would then generate a totally random string of that length, and then would
set about mutating it, finding mutations that were "fitter" than others (a
stand in for selection), breed the best two so far (randomly copy one chunk
from another to create a child), then repeat over and over.
When I first wrote it I wasn't sure what to expect; would it ever finish
given a reasonably large target string?
It did.
It was fun to code.
It got posted to the BB and of course wasn't in any way persuasive to them
(honestly I never expected it would be). I seem to recall it being
hand-waved away with calls of there obviously being an intelligent designer
involved.
Anyway, the "meanwhile..." in this: a few times this year I've thought it
could be fun to rework this in Python (it's really not that complex after
all; just a string-chopping loop really) and use Textual to put a fun UI on
it.
So, that's what I did, complete with textual-plotext
plot:
While, 15 years on, this isn't going to convince anyone of the underlying
point, I think it does serve a good educational purpose. It shows that you
can create a fun UI for the terminal, with Textual, with not a lot of code.
It also shows off how you can easily create dynamic plots. Plus -- and I
think this might be the really important one -- it shows you can write
"traditional" tight-loop code in a Textual application and still have a
responsive UI; all thanks to the worker
API.
The heart of the code for this application is this:
environment = Environment("This is the target string we want to create!")
while not environment.best_fit_found:
environment.shit_happens()
Sure, there's some detail in the Environment
class, but you get the idea:
while we've not hit the target, let life find a
way. A loop like that would
totally bog down an application with a UI without some other work taking
place. With Textual and workers the resulting method in the application,
complete with code to send updates to the UI, really doesn't look much
different:
@work(thread=True, exclusive=True)
def run_world(self, target: str) -> None:
worker = get_current_worker()
environment = Environment(target)
iterations = 0
self.post_message(self.WorldUpdate(environment, iterations, *environment.best))
while not worker.is_cancelled and not environment.best_fit_found:
environment.shit_happens()
iterations += 1
if (iterations % 1000) == 0 or environment.best_fit_found:
self.post_message(
self.WorldUpdate(environment, iterations, *environment.best)
)
if environment.best_fit_found:
self.post_message(self.Finished(iterations))
I honestly think the worker API is one of the coolest things added to
Textual and I so often see people have real "woah!" moments when they get
to grips with it.
Anyway... I've covered science, religion, and how Ruby is better than
Python, so I'm sure I've annoyed almost everyone. Job done I guess. ;-)
If you want to check out the app itself there's a GitHub
repo and it can also be
installed from PyPi using
pipx
.
Expect it to be my tinker project of choice for a wee while; there's a
couple of other things I'd like to add to it.