This is a list of blog-ish websites where I found insightful stuff
that I would like not to forget.
# [LispCast]
Eric Normand's musings on programming paradigms and their application,
with a soft spot for functional programming.
## [When in doubt, refactor at the bottom]
Quoting Sandi Metz:
> Duplication is far cheaper than the wrong abstraction.
The point being that blindly following the letter of the DRY law can
lead developers to add complexity to extracted functions because "it
almost does what I want; if I could add just one more parameter to
it…".
Normand and Metz encourage developers to "mechanically" extract small
pieces of logic; even if they are not re-usable, bundling things
together and naming them helps make the potential abstractions more
visible.
## [Programming Paradigms and the Procedural Paradox]
A discussion on our tendency to conflate *paradigms* with their
*features*; for example, when trying to answer "can this language
express that paradigm?", we often reduce the question to "does this
language possess those features?".
Normand wonders whether we do this because the procedural paradigm's
metaphor (a series of steps that each may contain any number of
sub-tasks) maps so well to its features (sequential statements,
subroutines) that it trained us to mix those up.
[LispCast]: https://lispcast.com/category/writing/
[When in doubt, refactor at the bottom]: https://lispcast.com/refactor-bottom/
[Programming Paradigms and the Procedural Paradox]: https://lispcast.com/procedural-paradox/
# [null program]
Chris Wellons's in-depth looks into a fairly wide range of programming
techniques and applications. The articles often come with
illustrative code samples, which are always broken down into
bite-sized chunks that are easy to grok.
Some recurring topics I enjoy reading about:
- GNU/Linux plumbing
- [Raw Linux Threads via System Calls]
- [Appending to a File from Multiple Processes]
- [A Magnetized Needle and a Steady Hand]
- C programming tricks
- [Global State: A Tale of Two Bad C APIs]
- [C Closures as a Library]
- [How to Write Portable C Without Complicating Your Build]
- [A Tutorial on Portable Makefiles]
- Algorithmics
- [Inspecting C's qsort Through Animation]
- [A Branchless UTF-8 Decoder]
- [Render Multimedia in Pure C]
- Emacs Lisp plumbing
- [Some Performance Advantages of Lexical Scope]
- [What's in an Emacs Lambda]
[null program]: http://nullprogram.com/index/
[Raw Linux Threads via System Calls]: https://nullprogram.com/blog/2015/05/15/
[Appending to a File from Multiple Processes]: https://nullprogram.com/blog/2016/08/03/
[A Magnetized Needle and a Steady Hand]: https://nullprogram.com/blog/2016/11/17/
[Global State: A Tale of Two Bad C APIs]: https://nullprogram.com/blog/2014/10/12/
[C Closures as a Library]: https://nullprogram.com/blog/2017/01/08/
[How to Write Portable C Without Complicating Your Build]: https://nullprogram.com/blog/2017/03/30/
[A Tutorial on Portable Makefiles]: https://nullprogram.com/blog/2017/08/20/
[Inspecting C's qsort Through Animation]: https://nullprogram.com/blog/2016/09/05/
[A Branchless UTF-8 Decoder]: https://nullprogram.com/blog/2017/10/06/
[Render Multimedia in Pure C]: https://nullprogram.com/blog/2017/11/03/
[Some Performance Advantages of Lexical Scope]: https://nullprogram.com/blog/2016/12/22/
[What's in an Emacs Lambda]: https://nullprogram.com/blog/2017/12/14/
# [Et tu, Cthulhu]
## [A hash table re-hash]
A benchmark of hash tables that manages to succinctly explain common
performance issues and tradeoffs with this data structure, to show
results across a wide range of implementations, and to provide very
understandable interepretations for those results.
[Et tu, Cthulhu]: https://hpjansson.org/blag/
[A hash table re-hash]: https://hpjansson.org/blag/2018/07/24/a-hash-table-re-hash/
# [Evanmiller.org]
I mostly only read the articles dealing with programming languages.
The down-to-earth commentary made me feel like the author both
appreciates the thought process that went into the design, and has
enough hindsight to find where that thought process fell short.
## [A Taste of Rust]
An overview of some of the language's features. Some comments
resonated particularly well with me, e.g. on nested functions:
> With other languages, I’m never quite sure where to put helper
> functions. I’m usually wary of factoring code into small,
> “beautiful” functions because I’m afraid they’ll end up under the
> couch cushions, or behind the radiator next to my car keys. With
> Rust, I can build up a kind of organic tree of function definitions,
> each scoped to the place where they’re actually going to be used,
> and promote them up the tree as they take on the Platonic form of
> Reusable Code.
[Evanmiller.org]: https://www.evanmiller.org/
[A Taste of Rust]: https://www.evanmiller.org/a-taste-of-rust.html
# [Bartosz Ciechanowski]
## [Alpha Compositing]
The good, bad and ugly of how we discretize colors, and
color-blending. With helpful interactive simulations.
[Bartosz Ciechanowski]: https://ciechanow.ski/
[Alpha Compositing]: https://ciechanow.ski/alpha-compositing/
# [Red Hat Developer]
## [10 tips for reviewing code you don't like]
The article could basically be included as-is in a [nonviolent
communication] textbook and renamed "application to code reviews".
AFAICT the underlying principle to all these tips is: scrub judgmental
statements out of your responses, and state your concerns openly.
Nobody should expect you to hold all the answers; express your
uncertainty, and let the submitter do the work of convincing you
(e.g. checking for performance regressions, splitting patch series).
[Red Hat Developer]: https://developers.redhat.com/blog/
[10 tips for reviewing code you don't like]: https://developers.redhat.com/blog/2019/07/08/10-tips-for-reviewing-code-you-dont-like/
[nonviolent communication]: https://en.wikipedia.org/wiki/Nonviolent_Communication
# Motherfucking websites
:::: tags
- Web design
::::
Satirical websites fighting [web bloat] with minimalist designs.
-
No style at all, content only.
-
Increased line spacing, bigger text, reduced line length.
-
Less contrast, better fonts.
-
Satire of the satire, to show that it's possible to be fancy
and stay lightweight.
-
Less contrast, better fonts.
-
Use fonts the user already have, compress content.
-
Think of internationalization.
[web bloat]: https://idlewords.com/talks/website_obesity.htm
# [Joe Duffy's Blog]
## [The Error Model]
An in-depth look at what "errors" are in the context of software, how
some languages choose to deal with them, and what model the Midori
team implemented.
> Our overall solution was to offer a two-pronged error model. On one
> hand, you had fail-fast – we called it abandonment – for programming
> bugs. And on the other hand, you had statically checked exceptions
> for recoverable errors.
Starts by outlining the "performance metrics" for a good error model,
then goes over unsatisfactory models:
- **Error codes** clutter a function's signature with an extra return
value, and the resulting branches degrade performance; they do not
interrupt execution unless checked manually, thus when bugs finally
show up, it can be hard to track their origin down.
- Though the Midori team did provide their developers with an
escape hatch that lets them ignore return values, their `ignore`
keyword is at least auditable.
- **Unchecked exceptions** make it hard to reason about a program's
flow. They persist because in the grand scheme of things, they stay
out of the way When Things Work™.
- **Exceptions in general** tend to come with some performance baggage
(e.g. symbols for stack traces), and encourage coarse error-handling
(i.e. throwing a `try` blanket spanning several statements instead
of focusing on individual calls).
All of these models conflate *recoverable errors* (e.g. invalid
program inputs) that the application can act on (by telling users
about their mistakes, assuming a transient environment failure and
re-trying, or ignoring the error) with *bugs*, i.e. unexpected
conditions that, when unhandled, create bogus states and transitions
in the program, and may only have visible impacts further down the
line.
As these bugs are "unrecoverable", the team chose the
"**abandonment**" strategy (aka "fail-fast") to deal with them.
> My impression is that, largely because of the continued success of
> monolithic kernels, the world at large hasn’t yet made the leap to
> “operating system as a distributed system” insight. Once you do,
> however, a lot of design principles become apparent.
> As with most distributed systems, our architecture assumed process
> failure was inevitable.
The micro-kernel architecture, where basic kernel features such as
"the scheduler, memory manager, filesystem, networking stack, and even
device drivers" are all run as isolated user-mode processes,
encourages "wholesale abandonment" as an error-handling stragegy,
since the failure remains contained and does not bring down the whole
system.
They implemented **contracts** using dedicated syntax and made them
part of a function's interface. Delegating contract-checking to a
library would have buried pre- and post-conditions as regular calls
inside a function's definition, whereas they wanted them to become
part of the function's metadata, where they can be analyzed by
optimizers, IDEs, etc.
> Contracts begin where the type system leaves off.
Contract violations trigger (at worst) program abandonment;
**type-system** violations plainly prevent the program from existing.
Since "90%" of their contracts were either null or range checks, they
found a way to encode nullability and ranges in the type-system,
reducing this:
public virtual int Read(char[] buffer, int index, int count)
requires buffer != null
requires index >= 0
requires count >= 0
requires buffer.Length - index < count {
...
}
To this:
public virtual int Read(char[] buffer) {
...
}
While preserving the same guarantees, checked at compile-time.
**Nullable** types were designated with `T?`; `T` implicitly converted
to `T?`, but conversion from `T?` to `T` required noisy
(i.e. auditable) operators which would trigger abandonment when
needed.
*Recoverable errors* were handled with checked exceptions, which were
part of a function's signature. Since most bugs were dealt with
through abandonment, most of their APIs didn't throw.
To make it easier to reason about control flow, callers had to
explicitly say `try` before calling a function that might throw, which
did what Rust's deprecated `try!()` did: yield the return value on the
happy path, else re-throw.
Covers performance concerns, composition with concurrency; muddies the
waters somewhat with "aborts" which kind of look like `longjmp`s to
me? Except it runs the code in `catch` blocks it finds while crawling
back the stack?
[Joe Duffy's Blog]: http://joeduffyblog.com/
[The Error Model]: http://joeduffyblog.com/2016/02/07/the-error-model/
# [Without boats]
## [Not Explicit]
:::: tags
- Rust
- Language design
::::
Picking up where Aaron Turon's ["reasoning footprint"] post left off,
and breaking down "explicit" into more precise epithets:
"Noisy"
: `try!()` is noisier than `?`, which works in the latter's
favor.
> Often - especially when forwarding errors, as `?` does - the early
> return is the least important part of the source code to me.
"Burdensome"
: aka "syntactic salt"; can be deliberate in order to
discourage.
"Manual" or "opt-in"
: e.g. if Rust required an explicit `.drop()` on variables, instead
of systematically freeing them when they go out of scope. As
things stand, a variable's eventual de-allocation is "explicit",
as in, it follows non-ambiguously from reading the source code;
yet it is also **automatic**.
"Local"
: "explicit within some scope". Method resolution is explicit (as
in, deterministic and unambiguous) but one('s IDE) must jump
around to figure it out. `?` is local to the very line where the
early return happens.
[Without boats]: https://without.boats/
[Not Explicit]: https://without.boats/blog/things-explicit-is-not/
["reasoning footprint"]: https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html