tag:www.matijs.net,2005:/blog/articlesToxic ElephantDon't bury it in your back yard!2022-04-08T05:53:21ZPublifyurn:uuid:a13ee1c7-27ff-41a5-8fba-997a674364a32022-04-07T12:01:24Z2022-04-08T05:53:21ZmatijsLet's do some old-school blogging<p>Remember that blog comes from weblog, and (I believe) originally was supposed to (also) mean a log of interesting stuff one encountered around the web, as opposed to a collection of largish articles about a specific subject. Remember that before Twitter, the small posts were also posted on blogs.</p>
<p>Let’s do some old-school blogging and post small stuff about random subjects, mostly links.</p>urn:uuid:fb810fae-19a7-4bcc-822d-62781656337b2021-12-11T09:26:07Z2021-12-11T09:27:01ZmatijsA few things I don't like about AsciiDoc<p><a href="https://docs.asciidoctor.org/asciidoc/latest/">AsciiDoc</a> is nice because a lot
is possible. However, there are a lot of surprising edge cases that make it
less great as an easy to read and write documentation format.</p>
<ul>
<li>
<p>Some list markers support nesting, others do not</p>
<p>If you use a dash (<code>-</code>) as a list marker, that works fine until you want to
create nested lists. Then, it turns out you should be using <code>*</code>.</p>
</li>
<li>
<p>Outdenting lists has very weird syntax.</p>
<p>To outdent a list, you have to add a number of empty lines equal to the
number of outdents plus one, followed by an empty line with a <code>+</code>. This is
too specific for a human-readable format.</p>
</li>
<li>
<p>Escaping only works where it is needed.</p>
<p>Wherever an asterisk leads to bolding, you can escape it using a <code>\</code> to create a literal
asterisk. However, when the use of an asterisk does not lead to bolding, adding the <code>\</code>
will just lead to a literal <code>\</code> in the output. Predicting where this will
happen is tricky so you have to constantly look at the actual output.</p>
</li>
</ul>urn:uuid:c8a66c75-0ac5-40e0-a100-8cf2cbea23c22020-10-10T14:32:42Z2020-10-10T14:32:42ZmatijsSome development automation<p>For a long time, part of my weekend routine has been updating the dependencies of all my open source <a href="https://www.ruby-lang.org/en/">Ruby</a> projects. I had set up some tooling to automatically run <a href="https://bundler.io/"><code>bundle outdated</code></a> on all my project directories and make a nice report. For good measure, it would also run <a href="https://docs.rubocop.org/">RuboCop</a> and tell me if any work was needed on that front.</p>
<p>I would then go through the list of needed work, adjust the dependencies (using <a href="https://github.com/mvz/keep_up">KeepUp</a> where possible), activate new RuboCop cops, fix new RuboCop offenses, create pull requests, wait for builds to be done and then merge. There actually was a certain satisfaction in keeping things up-to-date, keeping things neat.</p>
<p>A few weeks ago, I’d had enough. The process of keeping things up-to-date was starting to become tedious, and it was keeping me from writing actual new software. Having had good experience at work with <a href="https://dependabot.com/">Dependabot</a> I decided to automate dependency updates for all my open source repo’s.</p>
<p>After some experimenting I made the following changes to my repositories:</p>
<ul>
<li>
<p>I added a separate named RuboCop job as part of each repository’s Travis CI configuration. To do this requires using the <code>jobs</code> key instead of <code>rvm</code>, like so:</p>
<pre><code>jobs:
include:
- rvm: 2.5
- rvm: 2.6
- rvm: 2.7
- rvm: 2.7
name: "RuboCop"
script: bundle exec rubocop
</code></pre>
</li>
<li>
<p>I configured GitHub’s native version of Dependabot to create pull requests daily, using a file <code>.github/dependabot.yml</code> in each repository:</p>
<pre><code>version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: daily
time: "04:23"
open-pull-requests-limit: 10
</code></pre>
</li>
</ul>
<p>All this means is that the manual part has been reduced to just checking that the builds are green for the pull requests produced by Dependabot, and potentially any new issues found by newer versions of RuboCop.</p>urn:uuid:b2b90157-631a-48a7-a457-f91b2a35b3f62020-03-29T08:38:39Z2020-03-29T08:39:51ZmatijsGovernment<blockquote>
<p>Actually, problems only get solved because people roll up their sleeves and do shit, and government is the collective coordinating apparatus that helps us know what shit needs to get done and who needs to do it.</p>
</blockquote>
<p>Current Affairs, <a href="https://www.currentaffairs.org/2020/03/everything-has-changed-overnight">Everything has changed overnight</a>, <a href="https://mastodon.social/@AnnieGal/103898955906408464">via @AnnieGal@mastodon.social</a></p>urn:uuid:31440dae-18df-4bee-b8a0-bbce4a833e352020-03-20T13:08:33Z2020-03-20T13:08:33ZmatijsReleasing<p>Automating away your library release process because you find it boring and tedious is the worst thing you can do. People rely on your releases to be meaningful, have meaningful version numbers, and meaningful release notes. Yes, these take time. But your releases are when your users are reminded that you exist. At other times, your library is just quietly doing its thing. Releases are when your users take notice. They want to read your change log, look at the version number to see if they need to pay attention. You’re in the spotlight. This is your performance. Give your releases some love.</p>urn:uuid:4c70ae50-2829-4ca4-8c79-ecd0ce8d5e472018-12-31T15:46:20Z2019-04-23T07:21:41ZmatijsOpinions about Ruby gem development, 2018 edition<ul>
<li>Your next release should nearly always come from the <code>master</code> branch.</li>
<li>When updating your feature branch, prefer <code>git rebase master</code> over <code>git merge master</code>.</li>
<li>When merging your feature into <code>master</code>, prefer merge bubbles over squash merges and fast-forwards.</li>
<li>
<code>bundle exec rake</code> should run your tests.</li>
<li>You still should not check in <code>Gemfile.lock</code>.</li>
<li>Use RuboCop. Running just <code>rubocop</code> should do the right thing. If you need a specific version, add it to the Gemfile. In that case, <code>bundle exec rubocop</code> should do the right thing.</li>
</ul>urn:uuid:1432b8b9-ff78-432b-8ec5-18a20ac641f92018-09-20T09:03:17Z2019-04-23T07:21:41ZmatijsBetting<p>I happened upon
<a href="http://slatestarcodex.com/2018/09/19/book-review-the-black-swan/#comment-670327">this comment</a>.</p>
<blockquote>
<p>But more important, it just doesn’t work sensibly to explain why many people
decline modest bets (e.g. that someone not on the brink of starvation would
decline a 50/50 lose $100 vs gain $110) bet.</p>
</blockquote>
<p>You can look at this bet in two ways. The first is the single bet. Then, you
can think about how bad you feel about losing $100, versus how good you feel
about gaining $110.</p>
<p>The second way is as a repeated bet. And I think this is how people do think
about it: If I bet yesterday, why not bet today? Or, I lost yesterday, I need
to bet again today to ‘make up for it’.</p>
<p>Emotions aside, the reason given that the bet is a good one, is that <em>in the
long run</em> the better will come out ahead. But how long is the long run?</p>
<p>Let’s fire up <code>irb</code>. (I’ve reformatted the lines a bit to fit in an article layout.)</p>
<pre><code>>> def bet; rand < 0.5 ? -100 : 110; end
>> count = 0; sum = 0; while sum < 1; count+= 1; sum += bet; end; [count, sum]
=> [81, 90] # Oops!
>> min = 0; count = 0; sum = 0; \
> while sum < 1; count+= 1; sum += bet; min = sum if sum < min; end; \
> [count, min, sum]
=> [35, -530, 70] # OOPS!
</code></pre>
<p>Maybe you can spare $100, but can you spare $530? (Not to mention the fact that
many people can’t spare $100.).</p>
<p>Or even $1340, leading to a $50 win after 136 bets?</p>
<pre><code>=> [136, -1340, 50]
</code></pre>
<p>What are the chances of a repeated bet ruining you before you gain <em>anything at all</em>?</p>
<pre><code>>> def compound_bet; min = 0; count = 0; sum = 0; \
> while sum < 1; count+= 1; sum += bet; min = sum if sum < min; end; \
> [count, min, sum]; end
>> def killer_bet(threshold); count, min, sum = compound_bet; min < -threshold; end
>> def killer_chance(threshold); 100000.times.select { killer_bet(threshold) }.count / 1000.0; end
>> killer_chance(500) #=> 8.017
>> killer_chance(1000) #=> 3.532
</code></pre>
<p>A betting scheme with a 3.5% chance of losing $1000 doesn’t sound so good…</p>
<p>(The commenter goes on to point to <a href="http://faculty.som.yale.edu/florianederer/behavioral/Rabin_Thaler.pdf">an article</a> that actually doesn’t make the
claim that the given debt is a ‘modest debt’, and seems far more interesting
than that.)</p>urn:uuid:481c040e-9857-4aa7-bb5e-abcde1ccd9292018-07-25T06:59:14Z2019-04-23T07:21:41ZmatijsNo-one understands SemVer<p>I started reading <a href="http://technosophos.com/2018/07/04/be-nice-and-write-stable-code.html">this</a>, and came upon this line:</p>
<blockquote>
<p>Many people claim to know how SemVer works, but have never read the specification.</p>
</blockquote>
<p>And I thought: Yes! This is exactly the problem. Everyone talks about <a href="https://semver.org/">SemVer</a>, but no-one reads the specification, so the discussions don’t make sense. Finally, someone is going to Make Things Clear!</p>
<p>…</p>
<p>And then I read this:</p>
<blockquote>
<p>Note: Stop trying to justify your refactoring with the “public but internal” argument. If the language spec says it’s public, it’s public. Your intentions have nothing to do with it.</p>
</blockquote>
<p>What!? This person complains about people not reading the specifications, and then proceeds to contradict the <em>very</em> <em>first</em> article of the SemVer specification? <a href="https://semver.org/#spec-item-1">Here it is</a> (highlight mine):</p>
<blockquote>
<p>Software using Semantic Versioning MUST declare a public API. <strong>This API could be declared in the code itself or exist strictly in documentation</strong>. However it is done, it should be precise and comprehensive.</p>
</blockquote>
<p>Whether the language spec says it’s public has little to do with it.</p>
<p>Now, there’s a discussion going on on Hacker News about this article, and clearly I’m <a href="https://news.ycombinator.com/item?id=17597538">not the only one</a> bothered by the quote above, but the commenters are focused on whether languages allow you to control what part of your API is exposed, rather than what the SemVer spec actually says.</p>
<p>No-one understands SemVer.</p>urn:uuid:d9d9122c-498d-4c1c-9bed-d252acf106af2018-06-06T12:47:21Z2021-08-22T12:02:26ZmatijsImporting GTG tasks into Taskwarrior<p>I used to use <a href="https://launchpad.net/gtg">Getting Things Gnome</a> (GTG) to keep my TODO list. However, the project seems dead right in the middle of its Gtk+ 3.0 port, so I’ve been looking around for an alternative. After much consideration, I decided on <a href="https://taskwarrior.org/">Taskwarrior</a>. I wanted to keep my old tasks and couldn’t find a nice way to export them from GTG, let alone import them into Taskwarrior. So in the end I decided to create my own exporter.</p>
<p>Getting Things Gnome keeps your tasks in some simple XML files in a known location. <a href="https://github.com/mvz/happymapper">HappyMapper</a> is ideal for this. I started out using its automatic mapping, but as my understanding of the GTG format deepened, I switched to explicit mapping of a Task’s attributes and elements.</p>
<p>On the other side, Taskwarrior can import simple JSON files that are super easy to create using <a href="http://ruby-doc.org/stdlib-2.5.1/libdoc/json/rdoc/index.html">JSON</a> from the standard library. The script below will output this format to STDOUT. It’s up to you to use <code>task import</code> to process it further.</p>
<p>I implemented this as a spike, so there are no tests, but I like to think the design I ended up with is quite testable. I get annoyed whenever code becomes cluttered, or top-level instance variables start to appear. So I tend to quickly split off classes that have a distinct responsibility. I may yet convert this to a real gem and see how easy it is to bring everything under test.</p>
<p>Finally, before showing the code, I should warn you that it’s probably a good idea to back up your existing Taskwarrior data before playing with this.</p>
<p>Here’s the code:</p>
<div class="CodeRay"><pre><span class="CodeRay"><span class="doctype">#!/usr/bin/env ruby</span>
<p>require <span class="string"><span class="delimiter">’</span><span class="content">happymapper</span><span class="delimiter">’</span></span>
require <span class="string"><span class="delimiter">’</span><span class="content">json</span><span class="delimiter">’</span></span></p>
<p><span class="keyword">class</span> <span class="class">Task</span>
include <span class="constant">HappyMapper</span></p>
<p>attribute <span class="symbol">:id</span>, <span class="constant">String</span>
attribute <span class="symbol">:status</span>, <span class="constant">String</span>
attribute <span class="symbol">:tags</span>, <span class="constant">String</span>
attribute <span class="symbol">:uuid</span>, <span class="constant">String</span></p>
<p>element <span class="symbol">:title</span>, <span class="constant">String</span>
element <span class="symbol">:startdate</span>, <span class="constant">String</span>
element <span class="symbol">:duedate</span>, <span class="constant">String</span>
element <span class="symbol">:modified</span>, <span class="constant">DateTime</span>
element <span class="symbol">:donedate</span>, <span class="constant">String</span>
has_many <span class="symbol">:subtasks</span>, <span class="constant">String</span>, <span class="key">tag</span>: <span class="string"><span class="delimiter">’</span><span class="content">subtask</span><span class="delimiter">’</span></span>
element <span class="symbol">:content</span>, <span class="constant">String</span>
<span class="keyword">end</span></p>
<p><span class="keyword">class</span> <span class="class">TaskList</span>
<span class="keyword">def</span> <span class="function">initialize</span>(tasks)
<span class="instance-variable">@tasks</span> = tasks</p>
<pre><code><span class="instance-variable">@tasks_hash</span> = {}
<span class="instance-variable">@tasks</span>.each <span class="keyword">do</span> |task|
<span class="instance-variable">@tasks_hash</span>[task.id] = task
<span class="keyword">end</span>
</code></pre>
<p><span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">each_task</span>(&block)
<span class="instance-variable">@tasks</span>.each &block
<span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">find</span>(task_id)
<span class="instance-variable">@tasks_hash</span>[task_id]
<span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">root_task</span>(task)
parent = <span class="instance-variable">@tasks</span>.find { |it| it.subtasks.include? task.id }
parent && root_task(parent) || task
<span class="keyword">end</span>
<span class="keyword">end</span></p>
<p><span class="keyword">class</span> <span class="class">TaskProcessor</span>
<span class="keyword">def</span> <span class="function">initialize</span>(task_list, handler)
<span class="instance-variable">@task_list</span> = task_list
<span class="instance-variable">@handler</span> = handler
<span class="instance-variable">@processed</span> = {}
<span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">process</span>
<span class="instance-variable">@processed</span>.clear
<span class="instance-variable">@task_list</span>.each_task <span class="keyword">do</span> |task|
<span class="keyword">next</span> <span class="keyword">if</span> <span class="instance-variable">@processed</span>[task.id]
root = <span class="instance-variable">@task_list</span>.root_task(task)
process_task root
<span class="keyword">end</span></p>
<pre><code><span class="instance-variable">@task_list</span>.each_task <span class="keyword">do</span> |task|
raise <span class="string"><span class="delimiter">&quot;</span><span class="content">Task </span><span class="inline"><span class="inline-delimiter">#{</span>task.id<span class="inline-delimiter">}</span></span><span class="content"> not processed</span><span class="delimiter">&quot;</span></span> <span class="keyword">unless</span> <span class="instance-variable">@processed</span>[task.id]
<span class="keyword">end</span>
</code></pre>
<p><span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="predefined-constant">self</span>.<span class="function">process</span>(tasks, handler)
new(tasks, handler).process
<span class="keyword">end</span></p>
<p>private</p>
<p><span class="keyword">def</span> <span class="function">process_task</span>(task, level = <span class="integer">0</span>)
<span class="instance-variable">@handler</span>.handle(task, level)
<span class="instance-variable">@processed</span>[task.id] = <span class="predefined-constant">true</span>
process_subtasks task.subtasks, level + <span class="integer">1</span>
<span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">process_subtasks</span>(subtask_ids, level)
subtask_ids.each <span class="keyword">do</span> |task_id|
raise <span class="string"><span class="delimiter">"</span><span class="content">Task </span><span class="inline"><span class="inline-delimiter">#{</span>task_id<span class="inline-delimiter">}</span></span><span class="content"> already processed</span><span class="delimiter">"</span></span> <span class="keyword">if</span> <span class="instance-variable">@processed</span>[task_id]
task = <span class="instance-variable">@task_list</span>.find(task_id)
process_task task, level
<span class="keyword">end</span>
<span class="keyword">end</span>
<span class="keyword">end</span></p>
<p><span class="keyword">class</span> <span class="class">TaskWarriorExporter</span>
<span class="keyword">def</span> <span class="function">initialize</span>(task_list)
<span class="instance-variable">@task_list</span> = task_list
<span class="keyword">end</span></p>
<p><span class="keyword">def</span> <span class="function">handle</span>(task, level)
status = <span class="keyword">case</span> task.status
<span class="keyword">when</span> <span class="string"><span class="delimiter">’</span><span class="content">Dismiss</span><span class="delimiter">’</span></span>
<span class="string"><span class="delimiter">’</span><span class="content">deleted</span><span class="delimiter">’</span></span>
<span class="keyword">when</span> <span class="string"><span class="delimiter">’</span><span class="content">Done</span><span class="delimiter">’</span></span>
<span class="string"><span class="delimiter">’</span><span class="content">completed</span><span class="delimiter">’</span></span>
<span class="keyword">when</span> <span class="string"><span class="delimiter">’</span><span class="content">Active</span><span class="delimiter">’</span></span>
<span class="string"><span class="delimiter">’</span><span class="content">pending</span><span class="delimiter">’</span></span>
<span class="keyword">else</span>
raise <span class="string"><span class="delimiter">"</span><span class="content">Unknown: </span><span class="inline"><span class="inline-delimiter">#{</span>task.status<span class="inline-delimiter">}</span></span><span class="delimiter">"</span></span>
<span class="keyword">end</span></p>
<pre><code>data = {
<span class="key">description</span>: task.title,
<span class="key">status</span>: status,
<span class="key">uuid</span>: task.uuid,
}
<span class="keyword">if</span> task.duedate
<span class="keyword">if</span> task.duedate == <span class="string"><span class="delimiter">'</span><span class="content">soon</span><span class="delimiter">'</span></span>
data[<span class="symbol">:priority</span>] = <span class="string"><span class="delimiter">'</span><span class="content">H</span><span class="delimiter">'</span></span>
<span class="keyword">else</span>
data[<span class="symbol">:due</span>] = task.duedate
<span class="keyword">end</span>
<span class="keyword">end</span>
data[<span class="symbol">:end</span>] = task.donedate <span class="keyword">if</span> task.donedate
data[<span class="symbol">:scheduled</span>] = task.startdate <span class="keyword">if</span> task.startdate
entry = guess_entry(task)
data[<span class="symbol">:entry</span>] = entry
subtask_uuids = task.subtasks.map <span class="keyword">do</span> |subtask_id|
<span class="instance-variable">@task_list</span>.find(subtask_id).uuid
<span class="keyword">end</span>
<span class="keyword">if</span> subtask_uuids.any?
data[<span class="symbol">:depends</span>] = subtask_uuids.join(<span class="string"><span class="delimiter">'</span><span class="content">,</span><span class="delimiter">'</span></span>)
<span class="keyword">end</span>
data[<span class="symbol">:tags</span>] = task.tags <span class="keyword">unless</span> task.tags.empty?
<span class="keyword">if</span> task.content
data[<span class="symbol">:annotations</span>] = [ { <span class="key">entry</span>: entry, <span class="key">description</span>: task.content } ]
<span class="keyword">end</span>
puts data.to_json
</code></pre>
<p><span class="keyword">end</span></p>
<p>private</p>
<p><span class="keyword">def</span> <span class="function">guess_entry</span>(task)
dates = [task.duedate, task.donedate, task.startdate].compact.
reject { |it| <span class="string"><span class="delimiter">%w(</span><span class="content">someday soon</span><span class="delimiter">)</span></span>.include? it }.
sort
dates.first || task.modified.to_s
<span class="keyword">end</span>
<span class="keyword">end</span></p>
<p>projects_file = <span class="constant">File</span>.expand_path <span class="string"><span class="delimiter">’</span><span class="content">~/.local/share/gtg/projects.xml</span><span class="delimiter">’</span></span>
projects = <span class="constant">HappyMapper</span>.parse <span class="constant">File</span>.read projects_file
tasks_file = projects.backend.path
tasks = <span class="constant">Task</span>.parse <span class="constant">File</span>.read tasks_file
task_list = <span class="constant">TaskList</span>.new tasks</p>
<p><span class="constant">TaskProcessor</span>.process(task_list, <span class="constant">TaskWarriorExporter</span>.new(task_list))</p></span></pre></div>urn:uuid:def98e00-94a2-49ab-b1ee-c7e133f78f2f2017-07-31T13:50:13Z2019-04-23T07:21:41ZmatijsCurrent thoughts on smart contracts<ul>
<li>Writing a contract such that the law is powerless to reverse it is
anti-democratic. Libertarians will probably love it, but in canceling out the
‘oppressive’ state it also cancels any protections offered by the state.</li>
<li>Trust is a fundamental basis of human interaction. Creating a trustless way
of cooperating allows agents to not be held accountable for actions performed
outside the contract.</li>
<li>Instead of the lame excuse ‘the law allows me to be an asshole’, we’ll get
‘the smart contract allows me to be an asshole’.</li>
</ul>