Puzzle Languages (2009)(prog21.dadgum.com) |
Puzzle Languages (2009)(prog21.dadgum.com) |
On the other hand, there are some other languages I find baffling. For instance, C++. Whenever I have to use C++, I find it horribly confusing. It takes me considerable effort to figure out how to structure my program in such a way that the compiler won’t, for instance, insert a destructor call before I’ve used an object. Or that I won’t run into any one of the considerable number of footguns C++ contains. This is a language I find truly puzzling.
For this reason, I suspect that what is a ‘puzzle language’ is mostly to do with what you’re familiar with and the way you think. For me, C++ is a puzzle language. For Hague, Haskell is a puzzle language. For both of us, J is a puzzle language — but I can easily imagine someone who can write programs easily in J.
I'm always in awe when I see the Advent of Code solutions from Norvig in Python, they often seem so elegant. But my head isn't wired to think of the problems that way.
Exactly. The entire blog post is a long way to say 'I am accostomed to think in C-derived languages'.
Of course a new language paradigm will be a puzzle, it takes a while for our brains to change/match the new paradigm.
The 'puzzle' aspect of the language isn't necessarily permanent.
The article distinguishes between prog-lang specific puzzles and "programming in general is a puzzle", but without a universal paradigm it's not clear what that is - for example, stateless functional programming might be a puzzle, but the "norm" requires memory/state management as a puzzle; imperative/declarative is the same - which imperative is the norm, it's not clear to me it's less of a puzzle to have to explicitly think about sequence and state change.
In my experience things get 'complicated' when you have to deal with a distributed system where multiple actors are querying and changing a shared state. (And that shared state can be stored in one or more databases and/or in files in a file system.)
The main benefit of non-puzzle languages is incremental progress - even when I am not solving the entire problem, I am building functions and data structures which can be used to both understand the problem domain better and eventually to construct a complete solution. Sure, that might lead to an initial inefficient solution and require some rewriting, but often it's "good enough" (tm). That is why scientific Python is so successful, even though the final result is usually a patchwork of poor choices held together by duct tape.
Some languages map objectively better on that paradigm, independent of personal experience or ease of use.
https://store.steampowered.com/app/370360/TIS100
The more the language shatters your pre-existing assumptions the more it looks like a puzzle.
I would definitely categorize J as having a strong puzzle culture. Many of the people who use it are just doing it for recreation and to stretch their brains, and insist on writing everything in "tacit" (J's version of point-free) style.
Personally, I'm more interested in building things. Most of my J and K code look like any other scripting language, but just very highly compressed.
I would agree that Haskell is not quite in the same category, and don't get the sense that Haskellers strongly favor a particular brand of bending over backwards to make things happen. But then again I'm not nearly as tuned in to the Haskell community, and he was writing this 14 years ago...
It is masked by the fact it is based on C which is non-puzzle. So you can always drop to get things done layer.
"readability" is an aspect of the reader, not the code.
> Eventually I got back to scheduling and again wrote a new kind of scheduling system in Common Lisp, which again they did not want to run in production. And then I rewrote it in C++. Now at this point I was an expert C++ user and really loved C++, for some value of love. But as we'll see later I love the puzzle of C++. So I had to rewrite it in C++ and it took, you know, four times as long to rewrite it as it took to write it in the first place, it yielded five times as much code and it was no faster. And that's when I knew I was doing it wrong.
[...]
> So I mean for young programmers, if everybody's tired and old, this doesn't matter any more. But when I was young, when I was young, I really, you know, when you're young you've got lots of free space. I used to say "an empty head", but that's not right. You've got a lot of free space available and you can fill it with whatever you like. And these type systems they're quite fun, because from an endorphin standpoint solving puzzles and solving problems is the same, it gives you the same rush. Puzzle solving is really cool. But that's not what it should be about.
Talk: https://www.youtube.com/watch?v=2V1FtfBDsLU
Slides and transcript: https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
Haskell has this, Rust has this (where you're enticed to imagine everything as moving in and out of memory with a certain flow), C++ has this (trying to imagine how you can take advantage of as many features as possible to get beyond C's expressive ceiling).
Scala definitely has this, if only by making it impossible to write more than 2 lines of Scala without having to make 4 decisions around language feature usage.
You can see attraction to this kind of whimsy in the community.
I would definitely place Ruby in the whimsical language category though. There's way too much fun being had with mixins in that ecosystem for it to be considered a "straightforward" language.
I immediately recognized that for me, Rust (which I am teaching myself) is a "puzzle language" centered around pleasing the borrow checker. Most of my time I find myself pleasantly enjoying the elegant design of the language and of the libraries written for it. But there is a solid chunk of my time spent knowing exactly what I want to happen and trying to figure out how to express that in a language that prohibits aliasing.
> A critical element of puzzle languages is providing an escape, a way to admit that the pretty solution is elusive, and it's time to get working code regardless of aesthetics.
and my mind immediately went to Rust and the "unsafe" keyword.
Phrased this way, it seems like they won't be puzzles in certain domain-specific areas; ie., when the naive problem suits their big idea.
So I'd say the 'puzzle issue' arises because big-idea langs are just not good for many problems.
For example:
> In Haskell and Erlang, the puzzle is how to manage with single assignment and without being able to reach up and out of the current environment.
You can say the same thing about Python: "Stupid language. I just want a block of RAM and a pointer. Pointer? Parlez vous memory access? Wo ist das Void-Pointeren? Where's that blasted phrasebook... " [1] Python has abstraction over direct memory access, Haskell has referential transparency and a type system that's both stronger and less confining than most type systems that get called 'strict' in the procedural world. Neither allow you to reach out of their environments, at least by default, and it makes both of them simpler and more regular.
[1] Non-English languages intentionally a bit misused. That's the point.
Obviously, the solution is to learn how the language wants to do things, and speak it like a native. This isn't about purity, really, just learning how to use tools as they're intended to be used.
I think you are misunderstanding the point of the article. The writer doesn't mean to say that his puzzle languages are bad, just that they take more thought up front.
I don't think that is bad, I think it can be a benefit. E.g. in Haskell you spend a bit more time upfront to get the types right and making sure everything is pure. The purpose of that is to have a program that won't run into any issues at runtime. With a language like Python you have something ready to fun faster, but at runtime you can expect some errors, so you will have to spend your time at that point.
It's hard to see the riddle some people would find in Python because we're all experienced programmers and imperative languages like Python were what we all started out on. For us, the riddle coming into them was learning how to program at all, so we didn't see the concepts we hadn't mastered as arbitrary stumbling blocks getting in the way of this new language being normal.
> I can usually bang out a solution to just about anything in Python. I update locals and add globals and modify arrays and get working code
Globals in Python? They do exist but why ever use them? Not using them in Python invalidates most of the puzzliness attributed to Erlang and Haskell.
> Each [Erlang] process captures a bit of relevant data in a small, endlessly recursive loop. Imagine dozens or hundreds of these processes, each spinning away, holding onto important state data. Erlang string theory, if you will.
Calling an Erlang process to store state is equivalent to calling a method of an object. There are hundreds of important objects in any medium sized OO program, plus hundreds of thousands invisible ones, just because everything is an object. Nobody complains about creating the few important objects in Python and nobody complains about creating the few necessary processes in Erlang.
My complaint with Erlang and Elixir is that I have to define handle_cast and handle_call functions with the actual method name passed as argument instead of being able to define and use the real method names (or function names, it doesn't matter) as in any sensible language. It feels so low level, like OO programming in C around 1990. Erlang is excused because it's from that age. Elixir is not.
> A critical element of puzzle languages is providing an escape, a way to admit that the pretty solution is elusive, and it's time to get working code regardless of aesthetics. It's interesting that these escapes tend to have a stigma; they induce a feeling of doing something wrong; they're guaranteed to result in pedantic lecturing if mentioned in a forum.
I immediately think of the borrow checker, and its escape called "unsafe" directly induces a feeling of doing something wrong. Yes, rust would be a puzzle language by this criteria.
Agreed. But, is this what accounts for the oft-seen condescension ?
> The key point here is our programmers are Googlers, they're not researchers. They're typically fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They're not capable of understanding a brilliant language, but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt.
Prog21: Puzzle Languages - https://news.ycombinator.com/item?id=472327 - Feb 2009 (8 comments)
When I write code in an imperative language, I often spend a lot of time trying to find the optimal balance between complexity in the data and complexity in the control flow. As a dumb example, if you have a loop over some data that does one thing up to a certain point then another thing for the rest of the data, you can a) keep a Boolean that indicates whether the switch-over condition has been reached, or b) break the loop up into two loops, and fall through to the second loop once you hit the condition in the first loop. Without additional constraints I often prefer the latter (keep as much state in the control-flow as possible), but it can be very puzzling to figure out how to map your problem into a linear state machine, or to try to group lines of code into blocks that share invariants without accidentally overwriting some important data.
C is particularly designed around this kind of puzzling, and has features carefully built in to enable and encourage it, such as the pre- and post-increment operators, or assignment returning its RHS. C code considered elegant has usually been thoroughly optimized in this way. A prime example is this argument-parsing code from K&R [1]. When reading, I find it interesting to bear in mind that this is pedagogical code intended to teach people how to write the language, written by the authors of the language itself. Python tried to take away the ability to optimize for this sort of elegance by removing the syntactic features that support it, but the core puzzle still remains — you can carefully organize your code to ascribe meaning to things like loop breaks (especially with `except`!) or function returns. At its core this is because procedural code gives you a particular active data structure to work with (a stack of function calls each consisting of a set of mutable variables and a list of statements that mutate those variables, possibly with loops in) that is insufficient for encoding the natural structure of many programming problems, and you so have to make choices about which bits of the problem to map to it and which to code up manually with explicit additional data structures. This is not specifically a criticism of procedural languages: all other paradigms also limit you to one data structure or another, FORTH being the one that is most up-front about it.
In general, I think it is a fallacy of perspective to think that the difficulty of programming is broken down into ‘understanding what the code should do’ and ‘explaining that thing in your programming language of choice’. Rather, the problem of programming is precisely ‘understanding what the code should do in the computational model used by your programming language of choice’. If there is a universal model of computation in which we (as human programmers working on commercial timescales) can understand the meaning of programs in a way that is completely independent of the target language (such that mapping it to any target language is a purely mechanical process) I don't think we've found it yet. Someone steeped in a particular programming model will tend to consider their model effectively universal, and translating their mental model (expressed in terms of that programming model) into a different model of computation to be a puzzle; but in fact even encoding the ‘understood’ behaviour into a real-world programming language based on the same model usually involves making non-trivial decisions, which implies that our mental models of computational models are usually low-fidelity and/or may disagree with reality.
These are languages I'm familiar with:
...
These are languages I'm not:
...