Essays on programming I think about a lot
benkuhn.net
benkuhn.net
___
https://www.destroyallsoftware.com/screencasts/catalog/funct...
Unfortunately there's no transcript on the official video
Hickey clearly thinks about things from a systems perspective, which takes a number of years to play out.
You need to live with your own decisions, over large codebases, for many years to get what he's talking about. On the other hand, in many programming jobs, you're incentivized to ship it, and throw it over the wall, let the ops people paper over your bad decisions, etc. (whether you actually do that is a different story of course)
Junior programmers also work with smaller pieces of code, where the issues relating to code are more relevant than issues related to systems.
By systems, I mean:
- Code composed of heterogeneous parts, most of which you don't control, and which are written at different times.
- Code written in different languages, and code that uses a major component you can't change, like a database (there's a funny anecdote regarding researchers and databases in the paper below)
- Code that evolves over long periods of time
As an example of the difference between code and systems, a lot of people objected to his "Maybe Not" talk. That's because they're thinking of it from the CODE perspective (which is valid, but not the whole picture).
What he says is true from a SYSTEMS perpective, and it's something that Google learned over a long period of time, maintaining large and heterogeneous systems.
https://lobste.rs/s/zdvg9y/maybe_not_rich_hickey
tl;dr Although protobufs are statically typed (as opposed to JSON), the presence of fields is checked AT RUNTIME, and this is the right choice. You can't atomically upgrade distributed systems. You can't extend your type system over the network, because the network is dynamic. Don't conflate shape and optional/required. Shape is global while optional/required is local.
If you don't get that then you probably haven't worked on nontrivial distributed systems. (I see a lot of toy distributed computing languages/frameworks which assume atomic upgrade).
-----
His recent History of Clojure paper is gold on programming language design: https://clojure.org/about/history
I read a bunch of the other ones. Bjarne's is very good as usual. But Hickey is probably the most lucid writer, and the ideas are important (even though I've never even used Clojure, because I don't use the JVM, which is central to the design).
1. The simplicity of a system or product is not the same as the ease with which it is built.
2. Most developers, most of the time, default to optimizing for ease when building a product even when it conflicts with simplicity
3. Simplicity is a good proxy for reliability, maintainability, and modifiability, so if you value those a lot then you should seek simplicity over programmer convenience (in the cases where they are at odds).
If you agree with her hypothesis, what it's basically saying is that a clean design tends to feel like much more work early on. And she goes on to suggest that early on, it's best to focus on ease, and extract a simpler design later, when you have a clearer grasp of the problem domain.
Personally, if I disagree, it's because I think her axes are wrong. It's not functionality vs. time, it's cumulative effort vs. functionality. Where that distinction matters is that her graph subtly implies that you'll keep working on the software at a more-or-less steady pace, indefinitely. This suggests that there will always be a point where it's time to stop and work out a simple design. If it's effort vs. functionality, on the other hand, that leaves open the possibility that the project will be abandoned or put into maintenance mode long before you hit that design payoff threshold.
(This would also imply that, as the maintainer of a programming language ecosystem and a database product that are meant to be used over and over again, Rich Hickey is looking at a different cost/benefit equation from those of us who are working on a bunch of smaller, limited-domain tools. My own hand-coded data structures are nowhere near as thoroughly engineered as Clojure's collections API, nor should they be.)
Simplicity exists at every level in your program. It is in every choice that you make. Here's a quick example (in rust):
fn f(i) -> i32 { i } // function
let f = |i| -> i32 { i }; // closure
The closure is more complex than the function because it adds in the concept of environmental capture, even though it doesn't take advantage of it.This isn't to say you should never pick the more complex option - sometimes there is a real benefit. But it should never be your default.
You are correct in your assessment that customers typically request solutions to complex problems. This is called "inherent complexity" - the world is a complex place and we need to find a way to live in it.
The ideal, however, is to avoid adding even more complexity - incidental complexity - on top of what is truly necessary to solve the problem.
http://pages.cs.wisc.edu/~remzi/Naur.pdf
At the time I found it I was was working where the key challenge was not so much technical as much as ensuring that simple technology, spread across a large breadth of functionality, adhered to a consistent vision of the business domain that was being implemented. The program became an implementation of the mental model I developed of the business domain itself: its purpose, its uses, and its allowances and prohibitions. The Naur paper hit exactly not only on what I had been implementing in code, but on what other developers would have to know in order to maintain that code over time.... and had the kind of knowledge that had been lost over the life of application by the time I came to be involved.... and part of why my project existed.
https://www.csc.gov.sg/articles/how-to-build-good-software
It has a few great quotes scattered throughout.
"Building good software involves alternating cycles of expanding and reducing complexity."
"Software should be treated not as a static product, but as a living manifestation of the development team's collective understanding."
"Software projects rarely fail because they are too small; they fail because they get too big."
https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-a...
Some things should be DAMP every so often, and at regular intervals.
Now instead of /credit-card and /debit-card, which are independently testable, debuggable and changeable, you just have /card. Can't change the debit logic in /card because it will break credit. Can't change the credit logic in /card because it will break debit.
However the endpoint owner just insisted I just send nulls for the fields I didn't have.
https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction
My takeaway from it is that we need to distinguish what you might call essential from incidental duplication. Essential duplication is when two bits of code are the same because they fundamentally have to be, and always will be, whereas incidental duplication is when they happen to be the same at the moment, but there's no reason for them to stay that way.
For example, calculating the total price for a shopping basket has to be the same whether it's done on the cart page or the order confirmation page [1], so don't duplicate that logic. Whereas applying a sales tax and applying a discount might work the same way now, but won't once you start selling zero-rated items, offering bulk discounts to which a coupon discount don't apply etc.
[1] Although i once built a system where this was not the case! In theory, the difference would always be that some additional discounts might be applied on the confirmation page. In theory ...
> DRY is about knowledge. Code duplication is not the issue.
https://verraes.net/2014/08/dry-is-about-knowledge/
> Every piece of knowledge must have a single, unambiguous, authoritative representation within a system
At the same time, too much DRY can over-complicate (and even obfuscate) your code. That's not the answer either.
Taste. Taste, experience, and wisdom. But I don't know how to give them to someone who doesn't have them. Maybe by pointing out the problems of specific things they're trying to do, in a way that they (hopefully) can understand and see why it's going to be a problem. Maybe...
Of course there's always the thermodynamic argument that "any subprogram has the permanent and externally-detectable side effect of increasing entropy in the universe by converting electricity to heat" but that is to me a bit of a Turing tar-pit of an argument.
More or less reliability in the datagram layers affects performance - for example, WiFi does its own retransmissions whereas ethernet does not, because WiFi uses a less reliable physical layer, and because you don’t want your packets to have to go from London to New York and back before you discover one of them was lost.
But reliability at the WiFi later cannot give your application the semantics of an ordered data stream, so it is not a substitute for TCP. You can replace TCP with a different transport protocol if you want different behaviour, eg SCTP or DTLS or QUIC, but in all cases they are providing a higher level abstraction than raw datagrams, not just (and not necessarily) more reliability.
2. Bottoms do not make abstractions leaky in some generalised sense. The "fast and loose reasoning is morally correct" result applies: any abstraction that would be valid in a language without bottoms is still valid wherever it evaluates to a non-bottom value.
Of course if you put garbage in at the lower levels (e.g. define a monoid instance that doesn't actually commute) then you will get garbage out at the higher levels (e.g. the sum of the concatenation of two lists may no longer equal the two lists' sums added together), but that's not the abstraction leaking, that's just an error in your code.
There are cache-aware abstractions if your situation warrants them. Of course if you abstract over a detail then you lose control over that detail. But that's not the same as a leak, and it's the very essence of programming at all; if the program needs to behave differently every time it runs, then creating a useful program is impossible.
> Or if you have a bad RAM chip.
That's another example of what I said about garbage in, garbage out. The fault isn't in the abstraction, the fault is the bad RAM chip. If you were manually managing all your memory addresses then a bad RAM chip would still present the same problem.
For the record, I meant "Get better theory, and your theory can also talk about the difference between practice and theory."
That abstraction leaks when the actual physical behavior of a RAM chip differs from the abstract specification that it implements.
I find the idea that an abstraction is leaky if different implementations of it perform differently to be fairly useless. I don't think it's a useful concept unless the abstraction captures the expected performance. If the abstraction doesn't give any performance guarantees, then the caller shouldn't have any performance expectations.
Similarly to abstractions around accessing a file on disk that might fail. The abstraction should account for potential failures. If it doesn't account for failures, but the implementation does fail, then it's meaningful to call it leaky.
In fact, this is a common manner for abstractions to become leaky. You find you are in need some guarantee not present in the abstraction. You choose to add whether or not that guarantee is satisfied to the shared interface. Congratulations! You've added a leak to the abstraction.
But that's not the only option available. If you need a guarantee not provided by an abstraction, you could ignore the abstraction and use something that actually provides the guarantees you need.
For example, if I have to care whether the "collection" is implemented as a linked list or as a vector, the the "collection" abstraction has leaked.
I would even argue that's the point of an abstraction. Hide the details that don't matter to the caller. If performance is a detail that matters, and the abstraction doesn't capture it, then you're using the wrong abstraction.
I understood fine. He asserts that "always" on the basis of a handful of examples, only one of which even attempts to show anything more than a performance difference. It's nonsense.
A) not a law, but a principle
B) a tautology (formally the essence is “All non-trivial abstractions are non-trivial”)
(Let alone the weaselly “to some degree“)
"Not a law" is strictly tue, but the "law" idiom is totally in-line with "law of supply and demand," "law of diminishing returns," etc.
The tautology problem is not a problem. Tautologies are powerful. Douglas Adams has a great essay on this, but his novel version is more concise.
If you want strict laws and no "to some degree" hedges, read physics.
Which I equally detest.
> The tautology problem is not a problem. Tautologies are powerful.
They are, of course, but how is the statement tautologically useful in this context?
> If you want strict laws and no "to some degree" hedges, read physics.
Sure, but physics is not the only field that does not consist of, mostly, overly general, extrapolation of empirical, but ephemeral phenomena.
Yeah, this resonates. I've got in trouble with my wife more than once for being in "code mode" when I'm working and something happens, it seems to turn off my basic empathy for some reason. Programming changes people.
Some of my references: the famous Norvig view on design patterns [1] and also his view of clean code [2]. Knuth on programming [3] also really enlighting.
1. https://norvig.com/design-patterns/design-patterns.pdf 2. https://www.cs.umd.edu/~nau/cmsc421/norvig-lisp-style.pdf 3. http://www.paulgraham.com/knuth.html
https://www.berglas.org/Articles/ImportantThatSoftwareFails/...
It's been posted a few times over the years: https://news.ycombinator.com/from?site=berglas.org
I think part of the mystique of it is that the authors kind of faded into the background after publishing it. I've never been able to find a follow-up paper from them.
https://github.com/papers-we-love/papers-we-love/blob/master...
The authors' "ideal world" is one where computation has no cost, but social structures remain unchanged, with "users" having "requirements". But the users are all mathematical enough to want formal requirements. The authors don’t seem to notice that the arrow in "Informal requirements -> Formal requirements" (pg 23) may indicate that formal requirements are themselves accidental complexity. All this seems to illuminate the biases of the authors more than the problem.
Link to the paper, for convenience: http://curtclifton.net/papers/MoseleyMarks06a.pdf. I think it's a siren and takes away attention that could be spent on better papers.
This seems like a strange interpretation. I understand that the term "formal requirements" has a technical meaning in some disciplines, but I also think it's pretty clear that the author isn't using the term in that way.
It is much more likely that the author meant that users have requirements, but those requirements don't typically map cleanly to actions taken by the computer. This step of translation is necessary in the construction of a program, even if it is typically done piecemeal and iteratively.
"The problem with finding the right spec is more fundamental: we often don’t know what we want the spec to be. We think of our requirements in human terms, not mathematical terms. If I say “this should distinguish parks from birds”, what am I saying? I could explain to a human by giving a bunch of pictures of parks and birds, but that’s just specific examples, not capturing the idea of distinguishing parks from birds. To actually translate that to a formal spec requires us to be able to formalize human concepts, and that is a serious challenge."
"It’s too expensive doing full verification in day-to-day programming. Instead of proving that my sort function always sorts, I can at least prove it doesn’t loop forever and never writes out of bounds. You can still get a lot of benefit out of this."
The post makes a strong case, IMO. Formal methods can be valuable, but they don't have the track record yet for anyone to believe they're the #1 essential thing about programming.
Inventing on Principle https://vimeo.com/36579366