
On Scaling Mental Models - selff
https://buttondown.email/hillelwayne/archive/on-scaling-mental-models/
======
DmitryOlshansky
The problem with most great projects written by small group of brilliant
people in any language:

\- it introduces its own vocabulary, it looks like gibberish until you build
up the context

\- since the group is small and gifted they have no problem creating and using
the power tools of their own

\- there is no documentation so your only way is through experimentation and
direct communication

\- the folks who have built it had to move on to solve another (bigger)
problem, leaving no traces of what the vocabulary is, where the design was
headed, what should be changed with the new requirements and again next to
zero documentation

~~~
erik_seaberg
I don't see how learning a vocabulary could ever be harder than continually
trying to get the same concepts across without having vocabulary.

------
TuringTest
The problem with powerful languages is they're great for allowing you to
create new abstractions, but they're terrible at piercing that abstraction and
let you know what's going on inside when you need it (such as, when learning
what the abstraction means when you find it for the first time).

Current modern languages are built around the false dicotomy that you have to
choose between one of two representations, like those in the provided example:

1\. mydict1.merge(mydict2, handle_duplicates=(key, v1, v2) => v1 + v2)

2\. mydict1 + mydict2

A mid-level language will use the first style so that programmers will know
what's going on, but then the team will be stuck to using that style
everywhere even when they already understand that it's "oh, just adding two
dictionaries". Powerful languages let you use the second style but then, as
the article explains, it's hard for a new programmer to tell what's the mental
model behind that complex operation.

A good powerful Lisp-like language should allow you to build the second
abstraction in terms of the first, and use the simple syntax everywhere, but
then switch between both representations easily.

I.e. you should be able to _inspect_ the meaning of the more abstract syntax
by expanding its definition in place and see how it works in context.
Programmers typically address this shortcoming with support of powerful (yet
ad-hoc) IDE's runtime debugging tools. But why that approach is not integrated
into the language itself, I have no idea.

~~~
wyager
Another strategy: Instead of creating new abstractions willy-nilly (which will
probably be "wrong" in some sense you won't discover until much later, when
it's too late to drop it), you base your abstractions off something that
selects for "correct" low-entropy abstractions, e.g. by stealing ideas from
math.

~~~
hhas01
By all means, steal ideas from math. (Great artists, etc.) Math has had a
couple thousand years practice in learning how (and how not to) express
abstract ideas as formal written text.

But don’t _abuse_ math. Like redefining summation to mean any old thing under
the sun. Least of all when it’s a fricking union, for which the math symbol is
‘∪’, not ‘＋’.

~~~
noelwelsh
Both set union and number addition are examples of monoids, a maths concept
from the field of abstract algebra. So there is a maths abstraction that
unites these ideas.

~~~
xyelos
Uhh quick limiting thing here (although I agree that the addition symbol is
fine): the existence of a unifying math concept alone does not justify using
the "+" symbol. By that argument, notating permutation conjugation with a "+"
is fine, but that would be a notational sin as the "+" operator is reserved
for commutative operations.

However, with that disclaimer, noting that both operations form monoids and
both are commutative justifies the "+".

~~~
wyager
Haskell doesn't use the + symbol for monoids, it uses <>.

------
noelwelsh
The problem is programmers crafting the language to their own mental model.
One solution is languages that don't allow much abstraction. The other
solution is to craft a shared mental model. This latter approach is the more
powerful in my opinion.

Taking the example of adding maps, you (where "you" means the language
designers or language community) just need to define what `+` means for
primitive types and the composition follows naturally. What does adding maps
mean then? It means adding together the primitive types found under each key.
If everyone agrees 1) there is a `+` operation and 2) what it means for
primitives, there isn't much room for confusion. At some point you might want
to give this concept a name and then you have basically reinvented monoids.

This is one of the differences between my experience in untyped functional
languages (primarily Scheme) and inexpressive languages (primarily Java) and
typed functional languages (primarily Scala). In the former each abstraction
was a perfect snowflake crafted to the specific situation. In the latter we
just reuse existing abstractions. The problem with the former is you have to
learn the meaning and idiosyncrasies of each new abstraction. In the latter
you can leverage your existing knowledge in new domains.

~~~
hinkley
I’ve heard one of the early Agile luminaries say that if the terminology in
your code is different than the terminology of your domain that it’s a
code/design smell. Some day, if not already, that impedance mismatch will bite
you in the ass.

Architectural astronauts are fond of their own terminology. It’s nuts.
Especially if it’s from someone like my most recent perpetrator who tries to
use big words all the time and either gets them wrong or uses obscure meanings
that nobody else ever uses conversationally. You may be smart pal, but you’re
the biggest idiot I know.

At least the last astronaut was personable and had a hint of humility.

~~~
Terr_
> if the terminology in your code is different than the terminology of your
> domain that it’s a code/design smell.

Hence the idea/label of Domain Driven Design [0].

IMO like unit-testing it's valuable but you can't hope for 100%. There will
always be some concepts that are unique to how the data is being packaged or
calculated which aren't part of the business-domain, but hopefully they'll be
safely locked down beneath domain-centered abstractions.

[0] [https://en.wikipedia.org/wiki/Domain-
driven_design](https://en.wikipedia.org/wiki/Domain-driven_design)

------
hellofunk
This article also supports why the Go language has been so widely adopted and
successful, despite its young age. The language can be rather limiting and
somewhat disappointing for an individual developer, but offers great benefits
to the agility of a team.

~~~
wyager
> why the Go language has been so widely adopted and successful

That's because it has tons of funding and support from Google, not because
people did experiments and found that it worked better.

~~~
msla
If marketing was all it took, .Net would have destroyed Java back when Sun was
dying and Microsoft was still the monopolistic gorilla of the corporate
universe.

------
amboo7
How about this? [http://www.linusakesson.net/programming/kernighans-
lever/](http://www.linusakesson.net/programming/kernighans-lever/) You can try
with a smaller team and make everyone improve rather than complain.

~~~
karmakaze
I'd read the 'not clever enough to debug' bit before but not this description.
Like the diagram on 'flow state' though it usually a combination of lack of
time as well as proficiency that leads to frustration.

------
drewcoo
So language/tooling choice should be a sort of a Harrison Bergeron* affair, to
make sure that the least able to program will still be able? I'm not so sure I
agree with that. Nor do I agree with its obvious counter-proposal,
meritocracy.

I have heard of teams using "communication" to bridge these gaps but I know
little of that technology.

* [https://en.wikipedia.org/wiki/Harrison_Bergeron](https://en.wikipedia.org/wiki/Harrison_Bergeron)

~~~
erik_seaberg
You can't let anyone work at the top of the learning curve (where the payoff
is, where professionals belong) unless the whole team is capable of getting
there. This is part of what makes false positive signals in hiring so
damaging.

------
throwaway287391
> I didn’t use [vim] in 2017. That’s because my employer started doing more
> pairing, and nobody could pair with me. It was bad enough for the Atom
> users, but even the other vimmers couldn’t pair with me. They’d press
> something expecting the vanilla vim action and get something completely
> unexpected. It’d drive them crazy.

I've never done pair programming (other than one project when I was in school
and it was required -- bit of a nightmare IMO), but I thought the way it
typically worked was you switch off who "drives" (i.e. actually types the
code) in long intervals. So why couldn't each coder just use their own
preferred programming environment? Do they not each have their own machines?

~~~
modernerd
That's how it traditionally works, but some see this as a limitation. For
example, the pitch for Live Share for VS Code says:

> Each of you can open files, navigate, edit code, highlight, or refactor -
> and changes are instantly reflected. As you edit you can see your teammate’s
> cursor, jump to the location of your teammate’s carat, or follow their
> actions.

[https://code.visualstudio.com/blogs/2017/11/15/live-
share](https://code.visualstudio.com/blogs/2017/11/15/live-share)

I've found that letting both participants inspect and edit code together
collaboratively is useful when one participant is acting in more of a teaching
role. When the student gets stuck, you can jump in and demo right in the
editor instead of dictating code or ideas. It's useful if it's not overused,
like the second brake on the instructor's side in a driving lesson.

But if both participants are of equal ability, it just becomes annoying to
have dual controls in my experience. Hell is watching other people use
computers, but jumping in to effectively grab your colleague's keyboard and
mouse and say, “look, I'll do it” can be irritating.

The other main advantage is that, because you're streaming actions and not
video, you get a high definition view at all times instead of an image that
sometimes breaks up.

I think the OP is possibly talking about pairing in the same physical location
on the same machine, though, where custom vim setups can definitely get in the
way if you're swapping driver every hour or two.

------
carapace
> “powerful languages don’t scale”.

The "scale" he's talking about doesn't seem to be "of traffic" or "of data"
but rather "of programmers required to work on it".

> it’s hard for other people to work with you. They don’t share your mental
> model, and they don’t come in with all your initial assumptions. This is
> somewhat addressable if you all start working on the project together but
> falls apart when people join on later. The expressivity doesn’t scale.

So that's a failure of _documentation_ , eh?

~~~
hinkley
No, it’s often a failure of architecture or design.

People who build systems that only make sense to them are never going to
provide good documentation, no matter how often you ask for it. And it’s
always describing something that is already ‘done’, so there is very little
value as a feedback loop. You just stop asking when you either realize this is
all you’re gonna get or figure out how batshit what they’ve described is.

Sunlight is the best disinfectant, they say. Ex post facto documentation
provides almost none.

------
fjfaase
Articles like this are missing an important point. Namely, that generic data
structures in programming languages are used to represent rich semantic data
models. This is done by a process that I would call 'implementing'. It is a
mapping of a certain semantic model to, often a rather limited, set of
primitives. Some languages, are completely optimized for one type of data
structure. Take for example the relational database, in which everything is a
relation.

During the implementation process some knowledge is lost. What the author is
describing is a kind of merge operation. If you would know the semantic
meaning of the data that is being represented, it is rather obvious what
choice should be made. When a number represent a quantity, it is obvious they
should be added, but when a number is a kind of identifier, like an article
number, it is obvious that they should not be added. And yes, maybe in that
case the number should have been better represented as a string. In the
process of implementing it is often smart to represent it like a number,
because it is much easier to deal with a 64-bit number than a variable length
string.

~~~
noelwelsh
Your comment in a good example of the very different world views that exist
within programming. I'm not trying to bash on your comment, just illustrate
the difference and show how this makes communication trickier than we
sometimes acknowledge.

In a modern typed language you wouldn't represent an ID as an integer or a
string, you'd represent it is an ID. Then define addition as whatever makes
sense for IDs. This can mean not defining the operation at all, in which case
you can't compile code that tries to add IDs. This is the viewpoint that
statically typed FP people take, and the viewpoint that languages like Rust
take.

Your view is more inline with untyped languages or languages with weak type
systems like Java or Go. (Arguably Java is changing as it adopts more modern
features but I don't think the culture is changing as rapidly as the
language.)

~~~
msla
Very few use untyped languages anymore, and only to the extent people program
in assembly language or modify Deep Legacy stuff in Bliss. It's possible to
lose information in a dynamically-typed language, like Python, but that's a
matter of not using the language facilities, such as objects, and if you go
down that route, you can write stringly-typed Haskell.

~~~
noelwelsh
My mistake. I was using "untyped" in the sense it is used in programming
language theory, which is the same as the term "dynamically typed" in
colloquial programmer speak.

~~~
msla
And I'm pointing out how useless of a definition that is, because it renders
us unable to distinguish between some languages which do have and use type
information and other languages which do not. If a language has enough type
information to automatically convert a number into a string, it isn't the same
as one where everything is simply a machine word.

Plus, my comment about stringly-typed Haskell demonstrates that it's a
property of programs more than languages anyway.

------
harperlee
This is an interesting hypothesis, and I've also heard something similar said
of clojure: it makes lisp just a little bit less opaque (by way of macros not
being so popular, introducing {} and [] as a little bit of extra syntax,
emphasizing a very targeted philosophy on several aspects, etc.

------
trabant00
Just taking on the vim custom keys thing: that was a problem of
standardisation. That is why it did not scale. Nothing to do with mental
models.

~~~
mjfisher
I think that was more or less still the point - more expressive power
(including custom key bindings) allows you to build more complicated
abstractions with less obvious behaviours. It's more difficult to maintain a
mental model of those less obvious behaviours.

