The Hardest Program I've Ever Written (2015)
journal.stuffwithstuff.com
journal.stuffwithstuff.com
I wasn’t convinced that the exhaustive approach with weigths was a good idea. It makes it computationally super intensive and hard to predict what the printer would do.
Instead prettier runs on a simple idea: if something doesn’t fit in one line, break the outermost parent.
This simple rule (implemented via the Wadler IR) is efficient to implement and makes the output generally look good.
Then the hard part was to go through the very looong (took me ~6 months full time) process of adding special cases for every constructs that human write in a special way.
There is nothing in there that’s hard at a computer science level, it’s just a lot of special cases. I don’t believe that there’s a general solution for it, you just need to slog through it.
And the last annoying thing are comments, they can be everywhere and are super annoying to print correctly.
Overall, it wasn’t really hard, but a lot of work.
I find your approach much more reasonable than the one outlined in the article, which to me feels like complexity for complexity's sake. There might be some minor win to breaking lines by analyzing billions of possibilities, but it just seems like overkill, and it's certainly not predictable for the programmer.
f(a, b)
f(
a, b)
f(a,
b,
)
f(a,
b)
f(
a,
b,
)
My original motivation for prettier was this particular example where I was sick of having to add/remove all those newlines when the line would go below or above 80 columns. So prettier only allows the following two forms and will format code for you as you save: f(a, b)
f(
a,
b,
)(In particular, automatic refactoring tools don't need to wrap lines when they get longer.)
And everything seems to have worked out fine.
It's not the only way to do it, but I think it's interesting how getting everyone to agree that a problem doesn't need solving sometimes simplifies things a lot.
If we want to do it automatically, we need to figure out an algorithm to do that. It turns out that if you use 80 columns as a heuristic, the vast majority of the code will look fine. A lot of people (myself included) tried to use different algorithms but couldn't beat that heuristic.
If you know of a better way, please let me know :)
I'm not sure what corpus was used to determine that heuristic (nor what tab size). But it would trash a lot the projects I work on.
The upside is that as a developer you don’t need to worry about formatting yourself as you can just write code in a bad way, save the file and it’s formatted. And as a team, you will no longer spend any time in code review arguing about formatting and doing roundtrips to change small nits.
If the result is good enough that you are willing to let go of your manual formatting, the upsides are nontrivial.
Optimal text width for readability is 4-6 inches or (typically) 50-75 characters. Code, with its indented nature, tends to fit nicely within this if you keep to the 80 column rule.
https://homepages.inf.ed.ac.uk/wadler/papers/prettier/pretti...
Yes, you are right on both accounts. I'm still not entirely sure it's the right approach for dartfmt.
I started with a corpus of code that was hand-formatted by people who I felt had great style. And then I worked backwards to figure out what kinds of formatting mechanics I would need to be able to automate that.
Unfortunately, there are some very common formatting idioms in Dart that really just don't play nice with any simpler formalism. For example, it's common to use embedded function literals as if they were blocks:
group("test some stuff", () {
test("test a thing with a very long"
"multi-line description of the thing being tested", () {
expect(thing, isSomeThing);
});
});
Note here how the body of the innermost function literal does not respect the surrounding expression nesting, even though it is technically all an argument to test().But in other cases, a function parameter should work like a normal indented expression:
things
.map((thing) {
...
})
.where((thing) => thing.isFoo);
.toList();
I probably could have said, "Well, these idioms are going away and this code will look different." and then designed a much simpler formatter. A huge part of software engineering is picking the right problem to solve and knowing when to push against requirement and when to take them as given.For dartfmt, I did my best to keep the original requirements even thought it meant a much more complex formatter. That made my implementation job harder. But it made the political job of convincing everyone inside Google (and most users outside) to use dartfmt much easier because the output was closer to what they wanted.
I couldn't have said it better :)
I added a ton of special cases in the step that takes an AST and outputs the IR to handle those two things (and many many more).
For 1), I special case functions that look like `test(` and completely ignore the 80 column rule so that the name stays in one line. https://github.com/prettier/prettier/blob/3e0dceda9954658860...
For 2), member chains is actually the most complex piece of prettier, but it was important to get right. There's plenty of comments in the implementation: https://github.com/prettier/prettier/blob/3e0dceda9954658860...
I'm sure you did something similar as well :)
https://github.com/dart-lang/dart_style/blob/master/lib/src/...
For me, the indentation rules for those two examples are identical, because of a different rule: the adjacent-string concatenation means the two editor lines are one conceptual line.
"We sat down one morning," recalls Steele. "I was at
the keyboard, and he was at my elbow," says Steele.
"He was perfectly willing to let me type, but he was
also telling me what to type.
The programming session lasted 10 hours. Throughout
that entire time, Steele says, neither he nor Stallman
took a break or made any small talk. By the end of the
session, they had managed to hack the pretty print
source code to just under 100 lines. "My fingers were
on the keyboard the whole time," Steele recalls, "but
it felt like both of our ideas were flowing onto the
screen. He told me what to type, and I typed it."
The length of the session revealed itself when Steele
finally left the AI Lab. Standing outside the building
at 545 Tech Square, he was surprised to find himself
surrounded by nighttime darkness. As a programmer,
Steele was used to marathon coding sessions. Still,
something about this session was different. Working with
Stallman had forced Steele to block out all external
stimuli and focus his entire mental energies on the task
at hand. Looking back, Steele says he found the Stallman
mind-meld both exhilarating and scary at the same
time. "My first thought afterward was: it was a great
experience, very intense, and that I never wanted to do
it again in my life." - Guy Steele
Heck! to get a taste one can take a stab at writing a formatter for printing a floating point number.I recently did a coding test for a job where one of the tasks was to write a formatter for Dutch license plates. Typically these are in the format XX-99-YY, but there are... exceptions. At first glance it seems like it won't be too hard. But it ended up sucking up something like half of the whole coding test time.
I did beat it, with a lot of help from unit tests, but formatting is indeed a tougher problem than you'd expect.
Parsing/validity checking, on the other hand, could be cumbersome.
Based on the examples in that Wikipedia page, it looks like the correct rule is to insert a hyphen between a letter and a digit that are adjacent, or in the middle of a group of 4 letters or 4 digits. But maybe there are other edge cases where that fails.
Pretty-printers have the advantage never being finished, as there always are cases where some people would say “I would format that differently”. That’s an advantage because it also means you can declare any version that works decently “finished”. For formatting floats, it’s much more black or white. Your formatter either works or it is buggy.
The first working formatter for floating point numbers is from 1980, IIRC, and doing it correctly only became somewhat the expected case after Steelers 1990 paper (https://lists.nongnu.org/archive/html/gcl-devel/2012-10/pdfk...)
Unless you're simply talking about round-trip within the application itself, that's not possible.
OP's requirement is that the output for 0.1 + 0.11 is displayed as 0.21, and that the output survives a round-trip. Your example satisfies the latter but not the former.
Programming is fun, but sometimes it seems like you're cutting a tree down with a knife.
Between, I want to read more. Source of this story please?
> "We sat down one morning," recalls Steele. "I was at the keyboard, and he was at my elbow," says Steele. "He was perfectly willing to let me type, but he was also telling me what to type.
> The programming session lasted 10 hours. Throughout that entire time, Steele says, neither he nor Stallman took a break or made any small talk. By the end of the session, they had managed to hack the pretty print source code to just under 100 lines. "My fingers were on the keyboard the whole time," Steele recalls, "but it felt like both of our ideas were flowing onto the screen. He told me what to type, and I typed it."
> The length of the session revealed itself when Steele finally left the AI Lab. Standing outside the building at 545 Tech Square, he was surprised to find himself surrounded by nighttime darkness. As a programmer, Steele was used to marathon coding sessions. Still, something about this session was different. Working with Stallman had forced Steele to block out all external stimuli and focus his entire mental energies on the task at hand. Looking back, Steele says he found the Stallman mind-meld both exhilarating and scary at the same time. "My first thought afterward was: it was a great experience, very intense, and that I never wanted to do it again in my life." - Guy Steele
I hear you. It does suck on small screens.
But that's exactly what the leading space syntax is for -- to markup quotes. It works fine tablet/laptop/netbook etc but I hear that smaller form factors is a problem.
I think the right solution is to fix the layout so that it works form mobile. Not using the markup that's specially intended for quotes seems a wrong way of going about it.
Per the HN docs, leading spaces are meant for code, not quotes.
> "Text after a blank line that is indented by two or more spaces is reproduced verbatim. (This is intended for code.)"
https://news.ycombinator.com/formatdoc
HN doesn't have specific formatting for quotes. Many people use leading '>' along with quotation marks and italics to set off quoted text.
I don't mind, I get a lot of upvotes for posting those mobile friendly quotes. Just call me the Quote Fixer Bot. ;-)
Markdown recognizes the > quote convention and puts the text in a <blockquote> so it can be styled, typically indented a bit with a vertical bar on the left. I wish HN did that too!
A convention that is often used for quotes here is to both use the > and italicize them, like this:
> *You can quote me on this.*
which renders as:> You can quote me on this.
This is especially useful when you are interspersing quotes with your own text, as grzm did above. I can't make up my mind which I like better for longer multi-paragraph quotes: the italics make it look more like a quote, but I find long stretches of italic text harder to read.
The longest I did was 5 hours for ACM ICPC kind of stuff, but that left me wasted.
[0] https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...
Amazingly, surprisingly, counterintuitively, the indentation problem is almost totally orthogonal to parsing and syntax validation. I'd never have guessed it. But for indentation you care about totally different things that don't matter at all to parsers. Say you have a JavaScript argument list: it's just (blah, blah, blah): a paren-delimited, comma-separated, possibly empty list of identifiers. Parsing that is pretty easy. But for indentation purposes, that list is rife with possibility!
http://steve-yegge.blogspot.com/2008/03/js2-mode-new-javascr...
That’s usually where a lot of the hard problems lie, in my experience. Parsers, compilers, interpreters, sanitizers, and of course formatters are always a deep dive into formal language theory, where the limits are often fundamental rather than technical and you have to make tradeoffs to get a solution that works well.
You'd think it is a simple problem, but soon you will have a rabid animal on a leash and you will be struggling to restrain it.
I gave the source to a friend who modified it a bit and commited it to a language that was mentioned a couple of times on HN in the early days. It has since been replaced, but was used for a good 5 years.
I was going to write an inliner for a Lisp I was working on for a couple years. Emacs seems to have a decent one. But I know there are tricky corner cases.
Inlining is really hard.
Reading the inliners of languages like chez scheme or ghc is enough to give me a hardon.
return doughnutFryer
.start()
.then((_) => _frostingGlazer.start())
.then((_) => Future.wait([
_conveyorBelts.start(),
sprinkleSprinkler.start(),
sauceDripper.start()
]))
...
...
> (The funny names are because this was sanitized from internal code.)And for about 3 seconds I was really excited to learn more about who wrote this code!
The most useful research I did was actually around graph search algorithms. Branch-and-bound and most of the other stock pathfinding algorithms didn't work, but they gave me the pieces I needed to get to something that did.
Dynamic programming and memoization was key too. I got to use so many fundamental data structures and algorithms in this program. It was a joy.
Wider screens still have their advantages even if you keep your text narrow, mind - when you want to display multiple things side-by-side; the halves of a diff, or your code & a reference, for example.
The programmer spoke correctly.
The tl;dr for that project is that it runs dialyzer, finds the relevant Warning module for each warning, which will pretty print parts of the error output into a larger explanation, which involves taking the output, lexing it, parsing it, then pretty printing the IR back into Elixir, then running through the formatter.
That way all this hard work is off-loaded on human brains. They'll complain a bit but in practice will pick up very quickly where the line-breaks go.
(This would probably have to be a decision made by the language designers from the outset.)
"Some Dart users really dig a functional style and appear to be playing a game where whoever crams the most work before a single semicolon wins."
sort of the Dave Eggers of software engineering
If an optimizing compiler takes in a program decides to spit out the executable in a string (in base-64 format) which can later be converted to binary, using xxd for example, that makes the optimizing compiler "not pretty deep"?
Automated code formatting is fairly deep as far as I'm concerned.
can't imagine why all that mess is saner then putting a pretty printer on the other end of the language parser.
also this mess will be extremely non deterministic, depending on machine speed etc
He mentioned the algorithm questions he had to study to pass the interview and that knowing the algorithms came in handy.
If you are interviewing for Google, Facebook, Amazon, or Netflix, etc. solving problems that have never been solved at thier scale, algorithm questions are important.
If you just want someone to right the next software as a service CRUD app, don't waste my time.
However, Google uses the same interview process for all software engineers, and mostly decides what job they will do after deciding whether to hire them. So they really can't tailor their questions without changing their entire process. I think they should change it, but I understand why they haven't.