My Current State of Joshing Gentle Peevishness For the Awesome
and Terrible Majesty of the Towering Seas of Ire That Are
Themselves the Mere Milquetoast Shallows Fringing my Vast Oceans
| Day | Type | Main | Secondary | Assistance | Strongman |
| Mon | Heavy Upper | Bench | Row | kettlebell pullups kroc rows dumbbell bench | loaded carry |
| Tues | Heavy lower | Dead | Squat | kettlebell Lunges | |
| Wed | | OFF | | | |
| thurs | Upper | OHP | Rows | kettlebell Bands | Loaded carry |
| fri | Lower | Squat | Ruck Complexes | kettlebell Sprints | |
| sat | | OFF | | | |
| Sun | | OFF | | | |
Each day has a main lift, a secondary lift, an assistance, and a strongman thing. Assistances don’t matter much. I could technically do whatever the hell I want after main and secondary, but I prefer to only have one or two other things mixed in. The best assistance is the movement itself, after all. I’ll throw in the listed assistances, and maybe something else if I feel like it.
Secondary lifts are generally BBB-style sets: lower intensity, higher rep range. I prefer this, it gives a better sense of balance to a workout and to the week.
Everything is a multijoint compound movement, and I try to keep the ratio of push to pull as close to 1:1 as possible, to the point of doing more pushes than pulls.
Each workout is structured in the form of giant sets: main, assistance, secondary, rinse and repeat. Rest in between giant sets.
Mondays are heavy uppers: heavy bench, heavy rows, finished by kroc rows. Loaded carries.
Tuesdays: heavy lower: heavy deadlift, BBB-style squats, finished by lunges. Squats after the deadlift sets, this lets me get lower back volume without increasing deadlift volume to the point where it’d become risky and too draining.
Thursdays: Upper: heavy OHP, BBB-style rows. Loaded carries.
Fridays: lower: squats, and ruck/complexes. A lot of people would put the 2 day rest after deadlift, but a lot of people also don’t ruck after doing heavy squats. Fridays are the days I wish I had a prowler.
Squats: twice a week. One heavy, one not.
Rows: twice a week. One heavy, one not.
This routine gives me a nice balance between push/pull, uses every basic human movement, hits each muscle group with sufficient frequency, and provides enough recovery. It’s also flexible enough to allow me to plop new things in without changing too much: I could plug in almost any kind of periodization, cycle out deadlifts every other week with something else, adopt 531 or such, and really not alter the whole thing.
I believe that org-mode will drive you mad if you try to use more than the bare minimum of features necessary for what you need to do.
Here’s how I’m using it to track workouts.
I have a workout.org file, which is in
Workout.org has a local status header:
#+TODO TODO STARTED | DONE
The STARTED status is just handy to have, even if I don’t use it much.
workout.org contains the following structure:
I have the template
("w" "Log Workout" entry (file+datetree+prompt
"~/org/workout.org") "* %? %U" :empty-lines 1)
in my org.el which lets me create a new entry in the datetree. This entry is only to log details of the workout: weights, level of perceived effort, etc. The workout tasks are outside the datetree, in the schedule node. The schedule node contains subheaders of repeating scheduled todos:
** TODO Daily: 300 KB swings
DEADLINE: <2017-06-15 Thu +1d>
** TODO Monday: Squat/row
DEADLINE: <2017-06-19 Mon +1w>
etc. I can clock in, mark done to clock out, and then log the details in the datetree. Keeping the todo separate from the details seems like a good idea, as I don’t have to go hunting around: every workout todo is in a single spot. Each workout task has a link to a description in the regimen node: giant set blah, use these accessory movements blah, etc.
Note that these tasks are deadlines, not scheduled. org-mode considers a scheduled task to be when the task is to be started. A deadline task will appear as DEADLINE on the designated day, and org-agenda will show the number of days til a deadline.
The regimen node just contains such as giant set templates, notes on progressive increments, etc. Eventually I may use org-babel to add a such as a 531 calculator and template generator.
Stats contains my bodyweight, BF%, not in a datetree but just time stamped.
This is the simplest setup that does what I want. I could get fancier: adding tags, tweaking a report function that’ll show a plot of weights used for a specific lift over time, setting org-habit properties, writing a function to slurp the data right out of my phone’s FitNotes .csv into the appropriate places, etc.
None of that’s really necessary. FitNotes tracks and graphs specific movements and muscle groups and weights much more nicely than I would do myself. Org-habit, while useful for some things, doesn’t really make sense in this context. And directly integrating FitNotes data seems redundant. What workout.org does allow that fitnotes doesn’t, is the ability to track and schedule things in plaintext, portably.
The one justifiable increase in complexity is the abovementioned desire to incorporate a workout calculator and generator directly into the file via org-babel. For instance, being able to generate a datetree pre-filled with 531 workouts for a specified number of mesocycles. However, I’m not doing 531 although I am inspired by it. If I do switch over, I’ll probably get around to it.
That’s workout.org. It uses barely any of org-mode’s features, does what I need it to do, and can be endlessly refined into one of those org setups where the user seems to be attempting to use every feature of org-mode to weave an elaborate robot web around their lives.
The prime rule in emacs is to start from scratch. The absolute worst thing you can do is plop someone else’s stuff into your setup and expect it to work for you. In org-mode especially, this is vital. Every one of those absurdly-polished gems of org mode configurations you see, where the blogger seems to have automated themselves away, is the result of years of tweaks and personal itch-scratches built up over time in the way that only lisp allows. Few will want to show you how they did things the lazy way, only the final result guaranteed to utterly annoy anyone who plops it into their config and tries to use it. In org mode especially, that lazy way that doesn’t show off any of the really nifty features is probably good enough for 80% of the time, and can easily be used as a guide by a newbie. If they’re foolish enough to steal it for themselves, at least it’ll be simple enough they will be able to understand it and find out how to tweak it. Either way, it’s much more useful than showing the fully chromed-out inscrutable code that the config inevitably becomes as emacs is tweaked into conforming to them.
I’m running Crouton, which makes some things difficult and others outright cartoonish. Here is what was involved in getting Stack to work.
Round 1: I installed stack from gethaskellstack. My ppa ghc was 7.4, which is cartoonishly outdated and which stack will yell at you about if you try to use it as global ghc. So: stack setup and it’ll install ghc, right?
No. First, I found that stack kept repeatedly freezing while downloading ghc 8.2, then starting over again. Sometimes the resource would vanish. When it did manage to download entirely, it’d attempt to unpack all 1.4gb into about half that amount of tmpfs. I knew that the tmpfs thing was a known issue, so I cleverly set $TMPDIR=~/tmp. Then I discovered that stack was not obeying this environment variable, and persisted in attempting to jam 10 gallons of shit into a 5 gallon bucket.
My tmpfs was half of total ram, and I my free space was about 1.5gb anyway. I manually plopped the ghc–8.2 tarball into where stack expected it, bypassing stack’s dodgy download process, and then tried to see if I could bypass stack in building it, taking advantage of an actual tmp directory. No dice: just not enough actual space for that.
So I moved my chroot out of internal storage and into an sdcard with 50gb of free space. This move was one command, worked instantly, and I haven’t seen any performance hit whatsoever. My filesystem now had plenty of space for updating from trusty to xenial (and ghc 7.4 to 7.10, which stack won’t yell about).
So I upgraded to xenial. Everything went absolutely smoothly. The only thing I had to tweak was that tmux’s updated version collapsed several mouse options into one, so I just had to replace them with that one setting.
I had plenty of space, a modern enough ghc, and the will to keep fighting stack.
Round 2. Xenial has haskell-stack in its repo. I installed it, and began working on the tmpfs problem. I discovered that it wasn’t listening to fish; I had to switch to zsh in order to set $TMPDIR such that it’d actually listen. This turned out to be a known issue since 2015. Stack will yell at you if you attempt to use a version below 1.4, so I had to upgrade it before I could install ghc 8.2.
I opened zsh in a pane, exported TMPDIR=~/tmp, and ran stack upgrade. Switched to another pane, cd’d to tmp, and stuff was going on! Things were happening! Compilation failed at 27/34 modules, something about ZLib compression having set invalid code lengths. Hey, at least it wasn’t my fault. I ran it again, this time it said only 29 modules were needed. It compiled all of them, exited successfully, and the version was not changed. The successful exit was a lie, it had not actually upgraded to 1.4. In a rage I uninstalled it and wiped ~/.stack.
I knew now that manually plopping things where stack expects them works, and that stack needs a non-fish shell interaction.
Round 3. I installed stack from gethaskellstack, so I would have 1.4 and wouldn’t have to be distracted by upgrading it. I opened a zsh pane, set TMPDIR=~/tmp, and then ran stack setup —git. It yelled that there was no package index, tried to download it, and then gave up saying the resource had vanished. I tried stack update —git. This caused stack to scream that there was no package index, and then try to download it, and then exit after the aws resource “vanished.” I ran it again with -v and discovered that the mirror it’s pointing at, simply doesn’t respond. It turns out this is a known issue, as well.
So: I needed to get that package index without relying on the mirror. I google-fu’d and found the json it was trying to get, and downloaded it into my frog static site. I redirected the url stack update was trying to get to into a localhost frog server, and voila. It installed the package index, and exited successfully. I then ran stack setup —git -v, and it miraculously began downloading ghc–8.2 without any interruptions. It downloaded the entire thing, then froze on installing ghc for 48 minutes. Then, exited successfully.
Right now it appears that stack is up and running, with a local ghc that is sufficiently up to date that it should stop yelling at me.
I tweaked my language-of-the day script. There is now a major language of the day (racket, elixir, haskell) and a minor language of the day (cl, chicken, clojure, erlang, ocaml). Each day gets a major language and a minor language; the major gets more emphasis.
How much more can vary depending on mood, but this way not only does something get done in each language each day, but the three I am most interested in get more emphasis each day.
It seems like a nice setup. I’ll have to play around with it more to see where the annoyances are. The other day, for instance, was a haskell day but I got damned little accomplished due to trying to get stack to work properly.
The yakshave in question: no matter how I changed $TMPDIR and $STACK_ROOT and such, stack kept trying to download its local ghc into ~/.stack on my internal drive then cartoonishly fail at attempting to stuff it into my limited (I’m on crouton) tmpfs instead of using a tmp folder on an external drive with 2TB of space. Symlinks, according to github issues tracking, apparently work fine after stack setup has done its thing, but that wasn’t the problem I was having. I eventually concluded that it’d require serious yakshaving with executability of external drives and such, and gave up for now. In the end, I got little actual haskell done due to spending so much time trying to get its environment working.
Here’s my global org todo keywords:
'((sequence "TODO(t)" "STARTED(s)"
"|" "DONE(d)" "CANCELLED(c)")))
I have a YAKSHAVE org status that I can apply to tasks. I can C-c c a yakshave note (“figure out how to get stack to actually use $TMPDIR” etc) and keep doing what I was doing. I can clock in and out of it as I work on something, and see how much time was spent actually doing a task versus getting things to work. At some point I’ll log this ratio of task to yakshave by language, over time, etc.
Deferred/Next refer to tasks for a specific language that I’ll pick up again the next time its language-of-the-day rolls around.
What I’m doing is banging out a bunch of todos after language-of-the-day. C-c a t 1 r and I can see all of them. I can start one, C-c C-t it to started, and then make subtasks for it.
;; Enforce dependencies
(setq org-enforce-todo-dependencies t)
This makes a todo depend on sub-tasks. You can’t switch a task to DONE until every TODO in it is done. Yakshaves aren’t subtasks, and thus survive after the task is done as a reminder. Afterwards, I can switch the yakshave to a TODO, clock in, and start messing with it.
While writing this I popped out a yakshave task to see how to convert org source blocks to scribble code blocks. Github pages renders org and md, but I don’t know if I can tell it “this is a markdown file with org syntax you should also follow.” Hm, I wonder if frog can be made to deal with org files. In keeping with my goal of minimizing yakshaving, I decided to just indent the above code snippets into generic markdown blocks and play with it later.
I then made a note to figure out how to display clock minutes as pomodoro units just by dividing them by 25. I don’t really use pomodoro (I prefer to measure effort in cigarettes. A full gentoo stage one install is 1 pack. Getting Frog up and running: 3 cigarettes.) But, seeing things as 25 minute chunks is just more useful than seeing the raw amount of time spent. Doing the same thing later and seeing the effort drop is more impressive when you can see whole pomodoro units drop off rather than “oh it took 20 minutes less on each of these parts.”
I made a shell script that plops out a random programming language from the list of ones I’m playing with. I called it language-of-the-day.
In the morning, I add an entry to orgfile.org that drops the output of that script into today’s node and evals it. I then know what language to focus on that day.
I then C-c c and start adding todos for that language. Do foo, look up bar, try out baz, study source of quux, etc. C-c a t lets me see all these todos, and I can start working on them.
I can divide a todo into separate todo steps, set a time estimate for each, and see how long I expect the task to take. It’s possible to then compare the estimate with the reality, and see how laughable the former is. It’s possible to then look into the subtasks and see which ones were the most inaccurate. I can set a todo note to study whatever was causing that step to take so long.
Today’s language is Racket. I only have 3 todos: some exercism problems, studying the Racket style guide, and writing yasnippet/ultisnip templates specifically for Racket. The latter is gonna take a while, as I don’t feel at all like writing a snippet I will never use. I’d rather write code, note (C-c c n) when a snippet would come in handy, and keep working on it. Later, I can look at those notes and see which snippets would actually come in handy. I can also, without interrupting what I’m doing, make a note about inefficient editing habits. “find a better way to do blah” etc.
This workflow helps keep me from bouncing around too much, playing with trying the same thing in another language or editor or falling down the rabbit hole of yakshaving. I’m focusing on foolang, doing bar and quux, and can tell you exactly how long it took me to figure out how to frobulate the wibble. Afterward I can look the notes, spin them off into separate projects, remind myself that I really wanted to try out x in another language, etc.
It’s not a polished, highly automated set up for 2 reasons: I don’t like magic, and that way lays endless yakshaving. This way has enough rough edges that resisting the urge to yakshave them away is a good form of exposure therapy.
I’m spending some time getting org-mode configured the way I want. I’m trying to keep things as simple as possible, given how much yakshave potential there is in org mode. I have only a few files in ~/org:
Orgfile.org, which is the default general agenda file. It’s structured like this:
My blog.org is just a datetree.
Code.org is tough to figure out how to structure it. I initially thought of organizing by language, but I don’t like how that lacks telos. It encourages dicking around with no goal, just bouncing from thing to thing. I could organize by project, but that similarly lacks focus if there are multiple projects to bounce around in. I settled on just a datetree and tags. A datetree encourages a daily log of what I intend to do, what I actually did, and where I am going. I can just filter by tags and date until this becomes insufficient and I need to split off a project, in which case I’d rather add it to code.org instead of making a new orgfile. Again, I want to try as hard as possible to discourage dicking around bouncing from one thing to another without a goal.
Clojure is an ugly, sloppy hack on top of Java that should shame anyone who has ever written a lisp interpreter in any language. The core code is sloppy and unprofessional, the leaky abstraction of the stacktrace would get you failed in any compiler course. I mean it. There are weekend hacks of scheme compilers inside Java that do not expose the implementation so blatantly. It’s one thing to do so to push out something as fast as possible, but to almost a decade later still not see that sloppiness as a problem is inexcusable. Here’s Peter Norvig’s implementation of Scheme in Java. Notice how he took pride in his work.
Clojure is lisp for people who literally do not know what a lisp is supposed to be like. It’s as much a failure as Arc, except that arc would have passed a freshman CS course. Arc still shows better attention to implementation detail than Clojure.
Clojure: what a Java person’s idea of a lisp is.
Clojure is an abomination, and that is probably why it’ll surpass all common lisps combined. It’s the php of lisps. If you’re a teen, you should probably learn Clojure for the same reason your parents learned php. And when you get older, you’ll be able to laugh and cringe at how much needlessly sloppy, krufty bullshit you had thought was perfectly normal. Clojure: php for people who think they’re better than php programmers.
Now consider all the amazing lispy stuff being done in Clojure by future Elixir devs. Because it’s a lisp, even a shitty one, they are able to do incredible things which absolutely terrify their counterparts who arrived in Clojure from Java. That is a testament to lisp, that even in such a bad lisp people can do incredibly productive, cool things. All of these people will eventually leave Clojure and find a better lisp, and leave behind warehouses of interesting, mindbending lispy code that half of clojurians will regard with religious terror, like powerful magical artifacts. Those on the other end of the spectrum will leave for a more boss-friendly language and dev environment like Go. What will be left, the eternal core of Clojure, will be people who are content with using a bad lisp. The goldilocks programmer, stuck in the middle, who simply puts up with the glaring warts and unfinished, sharp edges. Who shrugs while pounding a nail using a garden gnome instead of a hammer, but accepts that it’s better than whatever they’d be doing in Go or such.
The goldilocks Clojure programmer is one for whom Clojure is their first functional language, first serious use of lisp outside of a hobby since college, and who has suffered through a boss-friendly language for a bit before arriving in Clojure. This person doesn’t know what normal functional languages or lisps or like, but does know what the lack of Java is like. Unfortunately, they’ve normalized Java enough that they don’t notice how much squirts out of their code. This person will stay, outlasting the lisp weenies who arrive in a shower of happy parentheses and leave just as suddenly. This person will stay, outlasting their colleagues who spent more time in cubicle hell than they did, and who retreat to a more corporate language. This person will not have any sufficient motivation to fix either the errors the lisp weenies see, or the errors the Java people see.
Clojure is, almost by design, guaranteed to experience evaporative cooling from both ends of the political spectrum of coding. It’s an alt-center language, it’s that weird guy who claims to be both an anarchist and a Moldbugian. It’s a gloriously failed lisp that will almost certainly expose more people to lisp than any other dialect has. Just like php exposed an entire generation to their first, terrible, programming language.
I use Crouton on a chromebook. Clojure is a pain in the ass on low-end devices. Just annoying as hell, easily hogs all cycles. Lein spins up its own jvm, which adds to the startup time. On a low-end device, you’re gonna spin it down in order to such as watch a youtube video, so you really will not have long enough lein sessions to justify such a slow startup. On a heftier box, you don’t have to juggle things like that.
The stacktraces, holy shit. I hate Java. Every time I see 100+ lines of java vomitspit I see clojure telling me that it is not a real lisp, that it fundamentally will never understand concurrency, and that it does not intend to follow the Lisp Main Sequence.
The Lisp Main Sequence: All lisps inevitably try to consume their own stack. The normal evolution of a lisp is to ruthlessly divorce itself from its implementation, and become pure lisp. Lisps are like facehuggers. Every normal lisp wants to be lisp all the way down. This isn’t a technical thing, it’s social. Lisp hackers want to be able to hack lisp all the way down to the silicon. It’s engrained into the mindset. Lisp is meant to take over all that it sees, drive its enemies before them, etc. Deep down, even the smallest lisp wants to grow up to be a Genera lisp machine.
The mindset of clojure devs, meanwhile, is not the normal lisp mindset. After almost a decade, they have not taken over the stack. In any normal lisp, a year is sufficient. Clojure devs do not think like normal lispers; something is missing in their personalities. It’s downright unsettling to think that for almost a decade clojure hackers have dealt with java regurgitating at them in every stacktrace and put up with it instead of fixing it by replacing it with more clojure. It’s disturbing to think of the sheer volume of java vomitspit they have dealt with over the years as something that is perfectly normal in a lisp. It points to a fundamental defect, an inability to really grok lisp. It’s lisp for pointy-haired bosses. It’s lisp for people who wear ties. Horrifying.
Clojure seems to be at odds with its users. Half are from java, and don’t see anything wrong with java being spat at them. Half are from lisps, and inevitably try to hack their own mutually incompatible solution. The result is that there will never be a single, united front behind which the pre-gamed lispers can rally persuasively enough to get its benevolent dictator for life to adopt. The result is an incredible inertia compared to most other lisps. The userbase may grow, the number of libraries written in clojure may grow, but fundamentally it will not embrace the main sequence of lisp evolution.
Parodoxically, the inherent expressiveness of clojure (it is still a lisp after all), guarantees that a fuckload of effort will go toward scratching personal itches. New libraries and build tools will appear overnight, because in lisps they can appear overnight. But each must beat not only the java, but the other lisp itch-scratching. This will result in dissatisfied lispers bleeding out of the community in search of a lispier lisp. Before they leave, however, they’ll have produced so much sheer output in attempting to scratch their itches that the vast space of options will terrify the java faction. The java faction will similarly bleed off, in search of a more ordered and boss-friendly language.
Who remains? Those who are comfortable with java spewing shit at them, and who are not sufficiently lispy to attempt to hack up a solution in a weekend. Those who for whatever reason are at the borderline between the two mutually-exclusive poles of clojure’s community. In Yegge’s political spectrum of programming languages, clojure is that weird dude who appeals to both anarchists and the alt-right.
I see clojure increasing in popularity, but I don’t see it changing its fundamentally divided nature. It’ll have upswings in popularity, but it’ll be continually bleeding from both ends. This hemorrhaging may not kill it, but it guarantees inertia. The java people will bleed off into Go or something, the lisp freaks will bleed off into Elixir or something, and the core clojure community will remain just as eventually dissatisfying to both.
Note: I am still interested in clojure, despite these annoyances.
I tend to get flustered when learning languages. I bounce from one to another, trying the same thing in each. “oh cool how would I do this in foo? Ah, gotta yakshave my emacs for bar.” Since every programming language sucks, I end up endlessly plunging down rabbit holes trying to yakshave things to work properly and smoothly. My focus gets distributed among n languages, m setups, and p annoyances. In this vast space, I end up wandering stochastically and occasionally bumping into useful things. This is no way to conquer a territory and hear the dialup modem screeching lamentations of its robots as you chase them before you.
Little ends up getting done, and I end up working at a cartoonishly slow pace as I bounce around playing with everything. Despite learning, I end up with little to show for it, and the lack of structure means I end up following an interesting avenue and completely neglecting what is usually considered a vital part of the language.
Right now, I’m focusing on Racket, Elixir, Clojure, and Common Lisp. oCaml, LFE, and Haskell as background noise, not the main focus. Clojure is a bit lower priority than Elixir; it’s just not a good lisp and the dev is not fun on a low-end device. Elixir is a better lisp than clojure and it’s not even trying. It didn’t even bother to bring its bag full of parentheses to the party and it still kicks clojure’s ass. Reminder to rant about this later.
That’s a lot of opportunity to get distracted on several levels, from other languages to yakshaving dev environments and workflows for them.
For instance, my normal workflow involves vim-slime and tmux. Vim-slime sends text to a specified tmux pane, it doesn’t know or care what repl is on the other end. It doesn’t know or care about nrepl or swank or any of that. Just where to dump the text. I prefer this over magic. Well, in LFE slurping a file into the repl dumps all previously slurped macros and defs. The repl is more stateful than most, so the workflow is a little awkward. I was pondering how to tweak vim-slime to have an LFE mode that would on C-c C-c first save, then send over the text (c "@%"), or something. I could yakshave on this happily, but it’s a distraction. I’m trying to minimize as much as possible the temptation to yakshave, so I forced myself mid-fork of vim-slime into lfe-slurp to stop and just accept a bit of extra typing.
My attempted solution to the focus problem is simple. For focus, I’m using exercism. Each language is on a different track, each has defined tasks to accomplish and progress is tracked. Most tracks have similar tasks, which nicely satisfies my “oh man I wonder how I’d do this in foo” impulse, but in a structured and focused way. Yakshaving is kept to a minimum: while working in racket, I can pop over to another pane, type “langjournal,” and note a todo about yakshaving vim to work better with it. Later, I can yakshave, but I can keep focused on what I was doing instead of falling down the rabbit hole.
I also have an enormous folder of interesting code laying on my 2tb external harddrive, from language cores and projects to ebooks. I can browse, offline, all the code I could want. Obviously, without structure I could endlessly study this without ever doing anything useful. With even a modicum of enforced structure, this actually becomes useful, instead of a labyrinth through which I’m chasing my own tail.
While doing the erlang track, I saw that rebar.config hardcoded rebar3. I did the task, gsubbed rebar3 to rebar (v2 is what I had), and ran the tests. They passed, I made a note. Went on #erlang, asked about rebar3, and decided to upgrade. I bootstrapped it in less than a minute, reverted rebar.config, and it worked as intended. Minimal yakshaving.
I figure as long as I keep things structured, push yakshaving out of the way into its own activity (“today I’m gonna get vim to work better with lfe”), and avoid temptation, I’ll have a much nicer time than my usual habits provide. Then, I’ll actually get stuff done, comprehensibly, instead of amassing largely useless knowledge.
This blog is going to be part of my workflow, forcing me to maintain a coherency which is usually lost reinventing a local PLEAC.
On #Racket, samth linked to Exercism, a wonderful analog to $LANG Koans. Each exercise is a basic, small program which you code by satisfying the unit tests. Unlike, say, Clojure Koans, the Clojure Track of Exercism has you create 58 programs of various degrees of complexity, rather than a running through individual test assertions.
You code enough to pass the tests, then submit. You can keep tweaking your solution, and keep submitting it. On your dashboard, you’ll be able to see how your solution has evolved over time. You can also look at how other people solved the problem, discuss solutions, etc.
Exercism, with its emphasis on creating programs and then discussing them, is an even better way to introduce a language than a standard koan runner. Especially considering that if you already know the language, instead of solving the exercises you can easily submit new ones for others to solve.