7 ms·
On Scaling Mental Models
- noelwelsh 7y agoThe 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.
- chrisweekly 7y agoI can't parse your last paragraph, in which "Scheme and Java and Scala" (3 things) are compared as "former vs latter" (2 things).
- noelwelsh 7y agoMy bad, I missed a comma. The former is Scheme and Java. The latter is Scala.
- AzzieElbab 7y agoWrt to code sample in the original article, are you saying live would be so much easier if everyone knew what monoids are?
- noelwelsh 7y agoSaying life would be "so much easier" seems like a strong statement, but I expect programming would be. :-) When I read that example the semantics were obvious to me: adding a map (applying a monoid operation on a map) should add the elements (apply the monoid operation for the elements).
- oddthink 7y agoBut one of the examples of adding maps would be for it to mean "upsert", rather than "use the monoid for the value types". I think that's still a perfectly good monoid over maps (it's not commutative, but that's not a requirement). And yes, you can argue that using "+" implies commutative, and I can see that, but I think it's still a plausible meaning in this context. (I'm probably rabbit-holing here, but it's interesting.)
- noelwelsh 7y agoHow you handle this gets into the details of the programming language used for implementation. The usual implementation technique is type classes, and the issue of having multiple type classes (implementations of, say, monoid) for a given type is known as "type class coherence". In Scala you could just pass a type class instance explicitly. If Haskell you'd have to change the type.
- hinkley 7y agoI’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_ 7y ago> 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
- pron 7y ago> One solution is languages that don't allow much abstraction. I think we need to be careful here with what you mean by "abstraction." One kind, which we can call "syntactic abstraction" means the following: given a specific language, what portion of its syntactic patterns can be factored into a new construct that "abstracts" all of its instantiations. Languages with macros score very highly here. Another kind, which has been more studied mathematically, is what we can call "algorithmic abstraction" or "behavioral abstraction", which means that given a certain specification of a desirable observable behavior, what portion of the implementations of that behavior can be exposed with the same syntactic construct (the same API). On this front, Haskell has worse abstraction than Java (e.g. if you want to change the implementation of a map to support big data with disk writes, you have to change the syntactic construct), and a language like Rust has terrible algorithmic abstraction, as even different implementation details like memory lifetimes require different constructs. Every additional piece of implementation detail that is exposed in the syntax level (e.g. in the type) hurts abstraction. I don't think that proponents of exposing as much as possible in the type think that's a bad thing, though. Both of these kinds of abstraction allow you to "leverage existing knowledge in new domains", and both have a certain "mental" cost. I don't think it is possible to relate "abstraction" in general to any bottom-line result, especially as no language choice seems to show any big impact one way or another, but mostly cater to different personal aesthetic preferences.
- marmaduke 7y ago> craft a shared mental model. This latter approach is the more powerful Isn't the crux of why programming is a technical activity second and a social one first?
- jart 7y agoNotation is the word you want. For example, we can design our language to say ht[x]=y or HashInsert(hTable,key,value), but at the end of the day they both abstract computer science toil don't want to think about, e.g. should it cuckoo probe pascal triangle bucket tree big theta under the hood yadda. My opinion is that terse notation only gains consensus when it's divinely inspired. That's the word we've traditionally used to describe the np-complete social phenemonon that happens every so often, where some guy's arbitrary mental model manages to allure a lasting self-evident appeal, e.g. LISP, C, and Python. So in some ways, we can think of guys like John McCarthy as modern programming prophets. One interesting thing I noticed, reading John McCarthy's original paper, is he was actually trying his darndest to not introduce a new notation. He wanted LISP code to look normal, since it's usually not a good thing to come across to other smart people as claiming divine inspiration (see Dunning Kruger). I disagree with the conventional wisdom though. If you'd want to take the more powerful approach of crafting a shared mental model, my advice would be one of encouragement. You can absolutely be the next John McCarthy. Folks only use wordy notation like HashInsert(hTable,key,value) because they want to go the safer route of being a small successful part of a large corporate machine.
- trabant00 7y agoJust 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 7y agoI 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.
- amboo7 7y agoHow 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 7y agoI'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.
- fjfaase 7y agoArticles 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 7y agoYour 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 7y agoVery 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.
- hellofunk 7y agoThis 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 7y ago> 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.
- AzzieElbab 7y agoWell, this logic should also apply to Dart which is not seeing same level of adoption as Go. Disclaimer - I do not use Go.
- msla 7y agoIf 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.
- TuringTest 7y agoThe 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.
- jasode 7y ago>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. [...] But why that approach is not integrated into the language itself, I have no idea. Are you asking why _all_ programming languages don't make their own Abstract Syntax Tree a 1st-class concept for self-reflection and manipulation of its own syntax like Lisp? Because there are tradeoffs to that approach. If one thinks there are zero tradeoffs to Lisp's philosophy of "code is data", I contend one doesn't fully understand Lisp. Yes, self-inspection is a powerful device but even Lisp programmers[1] keep inventing new languages for others to use that do not have Lisp-like ASTs as 1st-class concepts. I ask people to really think deeply on why that's been happening for decades and comment with any insights. [1] e.g. Guy Steele a Lisp expert, works on creating the new Java language at Sun in 1994.
- 7y ago
- throwaway287391 7y ago> 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 7y agoThat'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 7y ago> “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 7y agoNo, 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.
- DmitryOlshansky 7y agoThe 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
- hinkley 7y agoCircular reasoning is hard to manage out of a project. Circular reasoning that has never been articulated is much worse. Some of the best ergonomic improvements to my own code have come from trying to explain why my code is so stupid and figuring out that there is a fix that’s easier than this apology. When it’s three people there are no apologies even offered, and almost no feedback.
- amboo7 7y ago3. You can actually try and understand most of what's been written through reading, given that you're allowed the time for that. It's an investment, prevents technical debt and promotes progress. No one is born with the mediocre skills of big teams, one can (almost) always improve.
- erik_seaberg 7y agoI don't see how learning a vocabulary could ever be harder than continually trying to get the same concepts across without having vocabulary.
- drewcoo 7y agoSo 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 7y agoYou 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.
- deleted 7y ago[deleted]
- harperlee 7y agoThis 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.