Threads for munificent

  1. 26

    I’ve used ruby for 10+ years, understand meta-programming very well, and think it is almost always a mistake. Indeed, every ruby feature that breaks locality of behavior is best avoided entirely. This includes mixins and inheritance.

    “magic” as a dismissive pejorative represents deep cultural wisdom. Please, keep using it.

    Could not disagree more with the take presented in the article.

    1. 4

      Honest question: If you’re preferred programming style avoids any of the kinds of code you can only write using a dynamically typed language… why not use a statically typed language? It seems like you’re already writing code that would be amenable to static types and doing that gives you all of the nice stuff provided by static analysis: refactoring, code navigation, better error checking, etc.

      1. 2

        the kinds of code you can only write using a dynamically typed language

        I’m not aware of any such code.

        1. 2

          To be clear, I didn’t say ruby was my favorite language (though I like it fine)… just that I’ve used it a long time professionally. I think starting on a new project, all else being equal, I would usually choose a static language for the reasons you mentioned.

          With that said, the equation is not exactly that simple in my opinion. This is something I’d need more time to articulate fully, but subjectively, I do think there is a different experience to coding in ruby/js/etc vs Go, or even something concise but statically typed like Haskell. I’m not sure what the value of that thing is, and it probably doesn’t make up for what you lose, but there is something there.

          In ruby specifically, the built in Enumerable methods are nice, and the syntax is pretty. I don’t consider these huge factors, but those would be the answers to your question.

          EDIT: Maybe one example of what that “thing” is, from J/APL, where the integers 0 and 1 are your boolean types, and this allows you to write stuff like +/ 1&p: to count primes. So there can be advantages to “loose types” as well as footguns, with none of this mitigating my complaints about features that break locality of behavior.

          1. 2

            Rich Hickey and Clojure has come up with Clojure.spec as a half way house….

            You can have “plain old data” and a mechanism to specify the shape of it and validate and generate plain old data.

            I’ll admit to being conflicted.

            I remember building the most amazing arrays of hashes of hashes of arrays… in perl…. and never being able to maintain what I wrote.

            I think he has an interesting idea, but I’m not convinced.

          2. 2

            This is a good question, but I think the question of “can you reliably determine which function is being called here without running the code” is more or less orthogonal to being able to statically determine the type of a given value.

            Most mainstream dynamic languages are bad at both, but languages like Erlang, Racket, Clojure, and Lua handle the first one fairly reliably (the main exception being methods, which are often not used very widely) without making any attempt to solve the latter.

          3. 1

            You sound like go programmer that is programming go in ruby. ;-)

            I’d rephrase your…

            Locality of Behavior is the principle that: The behaviour of a unit of code should be as obvious as possible by looking only at that unit of code

            …as..

            Locality of Behavior is the principle that:

            The behaviour of a unit of code should be as obvious as possible by looking only at that unit of code, assuming the dependencies that this unit of code invokes are well named, and their effect on this code is well understood.

            ie. You don’t have understand how the dependencies do it, just what they do, and in particular, what the effect on this portion of the code is.

            1. 1

              You sound like go programmer that is programming go in ruby. ;-)

              I learned Go years after learning ruby, and hated it at first but learned to appreciate it.

              The behaviour of a unit of code should be as obvious as possible by looking only at that unit of code, assuming the dependencies that this unit of code invokes are well named, and their effect on this code is well understood. ie. You don’t have understand how the dependencies do it, just what they do, and in particular, what the effect on this portion of the code is.

              I used to think this way too. No more. The dependencies have to all be explicit as well. That is, myDep.doSomething where myDep is explicitly passed to your code is fine. But doSomething where doSomething exists because of a mixin or from your parent class or because of method_missing or some other magic is no good.

              1. 0

                The point about “functionality inherited from parent” isn’t to provide magic to the child.

                The whole point is Liskov’s Substitution Principle. The child IS an instance of the parent class and can be used wherever the parent can be used.

                People keep stepping away from the L in SOLID and then wondering why it all hurts.

                LSP isn’t a guideline. It’s a mathematical rule. Violate LSP you have a bug, it’s that simple.

                If you think in terms of Design by Contract all the time, LSP (and complying with it) becomes intuitive.

                1. 1

                  Yes, yes, I know all the arguments, I’ve read all the books. I know all about SOLID and can write that kind of code just fine. I assure this is not a problem with me being uninformed, or having too little experience for the ideas to become “intuitive.”

                  It is quite the reverse, and my recommendation is never to use inheritance (EDIT: to clarify, I am talking about implementation inheritance. Programming to an interface is just fine). If this strikes you as a novel stance, just google “inheritance is evil”. If you want specific recommendations for decent articles, let me know and I can dig through my bookmarks.

                  1. 0

                    Eh, seen a bunch of that sort of articles go by.

                    I look at the examples they give and start screaming at the screen… yup, if you do that, of course it will hurt.

                    So Don’t Do That.

                    Not convinced.

                    I guess we’ll have agree to disagree… unless I have to maintain your code! :-)

          1. 9

            Finally getting my blog re-published. I started 11 months ago and last week picked up where I left off and finished all but two layouts.

            I haven’t felt like writing since 2018 but recently worked on a realistic galaxy simulation as a basis for a explorable game map and have managed to write approximately 16,000 words on the subject that I am now working on cutting back and serialising into a tutorial-like-walkthrough of how I went about researching and programming the simulation.

            1. 2

              I would be very interested in reading that once you have it online.

              1. 2

                Same.

            1. 4

              I strongly agree with the premise that it doesn’t matter how much information is in a book if you don’t read it. When recommending a book to someone, or just choosing one for myself, I try to be sensitive to that. One habit I picked up that helps is that I’m willing to skim a book if it’s not fun to read. I figure getting through it and only absorbing 20% is better than giving up and getting 0%.

              And a final guess is that books that are worth reading might not always be well-written.

              My experience is that this is very true, especially as you get into deeper material. The intersection of people who know some obscure difficult topic and people who can write well gets smaller and smaller the more obscure the topic is. Eventually you have to decide between reading something poorly written by someone who’s at least got the goods, an enjoyable read that misses the mark, or potentially no book at all. Given that choice, I’ve learned to grind through mediocre writing to get to the material inside.

              My trick—and I’m totally serious—is to leave the book in the bathroom and not take my phone with me in there. You can get through anything if it’s in relatively short bursts and you don’t have anything better to do.

              1. 35

                Well, I graduated from a film school. I have minimal knowledge of math and engineering (I dropped out of engineering after my second year). I still find a ton of value in SICP.

                I think it is completely OK to recommend SICP. It is just that it is not an easy book; it requires effort. That effort required varies from reader to reader, and it may require you to go fetch another book and study for a while before coming back. It is OK to be challenged by a good book. A similar thing happens with TAoCP as well, heck, that book set was so above my pay grade that sometimes I had to go wash my face with cold water and think.

                Now, what I think is important is to know when to recommend SICP or not. Someone says they want to learn programming basics fast because they’re in some bootcamp and need a bit of background to move on, then you’d be better recommending something more suitable.

                As for alternatives for SICP for those who don’t want to dive too deep into math and engineering, I really enjoy How To Design Programs which I’ve seen being described as “SICP but for humanities”.

                1. 8

                  As for alternatives for SICP for those who don’t want to dive too deep into math and engineering, I really enjoy How To Design Programs which I’ve seen being described as “SICP but for humanities”.

                  A side note, but another book that can work well as a prequel (or alternative) to SICP is Simply Scheme. In fact, that’s exactly how the authors describe the book.

                  The only trouble with SICP is that it was written for MIT students, all of whom love science and are quite comfortable with formal mathematics. Also, most of the students who use SICP at MIT have already learned to program computers before they begin. As a result, many other schools have found the book too challenging for a beginning course. We believe that everyone who is seriously interested in computer science must read SICP eventually. Our book is a prequel; it’s meant to teach you what you need to know in order to read that book successfully. Generally speaking, our primary goal in Parts I-V has been preparation for SICP, while the focus of Part VI is to connect the course with the kinds of programming used in “real world” application programs like spreadsheets and databases. (These are the last example and the last project in the book.)

                  Some of Bryan Harvey’s lectures on Scheme are available on YouTube. There used to be more, as I recall, but some are private now. A shame—I remember enjoying his lectures a lot.

                  1. 7

                    I found the first chapter or two of SICP to be uncomfortably math heavy. But my recollection is that after those, it’s relatively smooth sailing.

                    1. 4

                      I have to say comments like these and the GP are reassuring. I’m working through it now as an experienced programmer trying to formalize my foundational computer science stuff and just having a hard time digging in on the start. Not that I’m uncomfortable with recursion or Big O stuff, it’s just very information dense and hard to “just read” while keeping all the moving parts in your head space.

                    2. 2

                      Brian Harvey is amazing.

                      He also wrote a series of books called “Teaching Computer Science LOGO Style” or similar (He’s the author of USCB Logo).

                      I really enjoyed those books as I’m an avid LOGO fan. I’m still kinda sad that dynaturtles are effectively going to die because the only implementation still even remotely extant is LCSI’s Microworlds.

                      1. 2

                        I haven’t played with it but I know that Racket has a lang logo and turtle graphics.

                        1. 1

                          I’ve been wanting to learn Racket for a while. It’s next on my list after Javascript. Sadly it takes me years to truly grok a programming language to any level of real mastery :)

                    3. 4

                      In the realm of alternatives to SICP to teach programming, I’ve really enjoyed The little schemer and follow-up books.

                      1. 4

                        I love that book. Did you read through the latest one? The Little Typer. I haven’t yet moved past the seasoned schemer.

                        1. 2

                          The latest one is in the virtual book pile. But I’d like to get to it eventually. Thanks for the reminder. :)

                        2. 2

                          The Little LISPer and its descendants are seriously pure sheer delight in book form.

                          They embody all the beautiful playfulness and whimsy I LOVE in computers that has been sucked out of so much happening today.

                          Bonus points: Even if you could care less about Scheme/LISP you learn recursion!

                          1. 2

                            Bonus points: Even if you could care less about Scheme/LISP you learn recursion!

                            After struggling hard at trying to understand recursion in my freshman year with Concurrent Clean (the teachers pet language, a bit Haskell-like), this book made everything click. It also made me fall in love with Scheme because of its simplicity and functional high level approach. After the nightmare of complexity and weird, obscure tooling of Clean, this was such a breath of fresh air.

                            1. 1

                              I really need to sit down and work through The Little Typer :)

                          2. 1

                            I don’t get The Little Schemer. There doesn’t seem to be a point to it, something it’s working towards. I feel like I should be enlightened in some way in the end, but it just seemed to end without doing anything special. What am I missing?

                          3. 3

                            I like this approach. Recommend SICP, but make it SUPER clear that it’s a difficult book. It’s OK to get to a point where you say “This is over my head” and for those who are up for it, there’s an additional challenge there of “OK now take the next step! Learn what you need in order to understand”.

                            Not everyone has the time or the patience for that, but as long as we’re 1000% clear in our recommendations, I think recommending SICP is just fine.

                            However IMO it is THE WORST to recommend SICP to people who are looking for a quick and easy way to learn to program or learn the rudiments of computer science.

                          1. 4

                            I feel like pretty much every “design pattern” is an example of an “emergent language feature”, as the author named them. Which is what I dislike about Java-style OOP.

                            1. 8

                              Design patterns are descriptive, not prescriptive. Whenever objects are composed in a certain way, including composition of functions, then the corresponding design pattern describes the behavior of the composition. They don’t just emerge from languages you don’t like, but from any Turing-complete language.

                              1. 4

                                Whenever there is a pattern in your code, you should abstract it away. Some languages will allow you to do that, some won’t.

                                1. 8

                                  Expressive languages still have patterns, people just refuse to call them patterns because they think expressive languages don’t have patterns.

                                  1. 2

                                    Can you give an example?

                                    1. 3

                                      If nothing else, Nim’s metaprogramming tends to have certain patterns, like dispatching on the kind of an AST node.

                                      Also, XiDoc uses when isMainModule and not defined(js):, a very common pattern in Nim programs that want to both expose a library and an executable from the same file. Just because the pattern isn’t obnoxious to deal with doesn’t mean it isn’t a pattern, a thing repeated where needed. Janet solves that particular conundrum differently, and Nim’s is a riff on a similar pattern in Python.

                                      It is also worth calling out that many patterns are as much about the the way data is arranged as they are are about code being arranged.

                                      And one could argue that convention-based programming is all about fitting into a prescribed pattern.

                                      That being said, making patterns in code less obnoxious to deal with is nearly always pleasant, IMO.

                                      1. 1

                                        I’ll see if I can come up with one, though it might be tricky because I don’t think we have any languages in common

                                        1. 2

                                          Just use pseudocode.

                                          1. 1

                                            I don’t see how I could. Patterns are found by examining how people use languages in the wild, and there’s no pseudocode in the wild.

                                        2. 1

                                          Ruby is an ultra-expressive language and very strong on using patterns like attr_accessor and similar to manage that complexity. Most of those patterns end up as libraries, but their use is as schematic as using patterns in other languages.

                                          1. 2

                                            I wouldn’t call attr_accessor a pattern. It’s just a method. That’s like calling print a method.

                                            1. 1

                                              It’s the implementation of the “Accessor Pattern” in a method to encourage its use. Ruby also uses iteration as a general pattern over loops, and the method is called “each”. It uses conversion over casts, implemented through “to_” methods. Ultimately, all pattern use boils down to some form of using language features, and methods are just the most common construct in Ruby.

                                              Ruby is very brief in it’s pattern use, but there’s a ton of them to know to program it competently.

                                              1. 2

                                                Well, that’s exactly my point. The pattern is abstracted away into a method, so there’s no repetitive code.

                                                1. 1

                                                  Patterns are not about repetitive code. That may be related, but many patterns are around common structure and names.

                                                  Related discussion: attr_accessor :foo, :bar, :batz or three lines of attr_accessor :foo...? If DRY is your only principle, the first, but there’s a strong school that says the second is preferable.(*)

                                                  Java Code is repetitive, but strongly pattern-based.

                                                  (*) which is why i prefer Single Source of Truth over DRY. https://en.wikipedia.org/wiki/Single_source_of_truth

                                                  1. 3

                                                    In my opinion, what distinguishes a pattern from normal code is that you have to recognize it as a pattern instead of taking the code at face value. For example, if you see the following Java code:

                                                    public class Vector2 {
                                                        private double x;
                                                        public double getX() {
                                                            return x;
                                                        }
                                                        public void setX(double newX) {
                                                            x = newX;
                                                        }
                                                        private double y;
                                                        public double getY() {
                                                            return y;
                                                        }
                                                        public void setY(double newY) {
                                                            y = newY;
                                                        }
                                                    }
                                                    

                                                    If you just read the code without recognizing that it’s “just” two properties with getters and setters, you’ll have to think about what it does in the larger picture. In contrast, the Ruby version

                                                    class Vector2
                                                      attr_accessor :x, :y
                                                    end
                                                    

                                                    makes the intention clear.

                                                2. 1

                                                  IMHO, to_xxx is an anti-pattern. Making one type out of another should be a constructor on the target type. to_xxx in Java is an artefact of Java types being inextensible.

                                                  1. 1

                                                    How far would you take that? “Each class which wants to support serialization to string should add a constructor to the string class” seems a little extreme to me.

                                            2. 1

                                              Clojure (with-open), Common Lisp (with-open-file, with-open-stream) and Ruby (File.open have the with-foo pattern for scoped resource usage.

                                          2. 4

                                            One of the frustrating things about design patterns in software is that so much of the original intent and thought behind “patterns” was lost in the telephone game of people talking about software engineering over the years. Christopher Alexander and the Gang of Four would both tell you that if you can abstract it away, it’s—by definition—not a pattern.

                                            The key thing that separates patterns from data structures and algorithms is that patterns are slightly different every time they are manifested. A pattern is a guideline for how one could structure a solution to a problem, but not a solution itself. Taking a pattern and applying it still requires human judgement and understanding of the specific context in which it’s applied to know what to implement.

                                            1. 2

                                              Abstraction is not factorization. Yes, whenever there is a repetition in code, we should factor it out.

                                              1. 1

                                                Nah. That repetition is very likely to be incidental, and factoring it out has the effect of reifying an abstraction that is unlikely to stand the test of time.

                                              2. 1

                                                This sentence implies definitions of “pattern” and “abstraction” that aren’t familiar to me. Can you say more?

                                                1. 2

                                                  A pattern is something that is repetitive, either directly or on a more abstract level. If you have a lot of the same code, it violates the DRY principle and you should extract it into a procedure/function/method. However, this won’t always be possible when the repetition is more nuanced. In this case, languages like Java will simply say “fuck you” and leave you to either write a lot of repetitive code, or even use a code generator to write it automatically, which produces heaps of code that is hard to navigate through and find the true substance. Meanwhile, a more powerful language will offer features like templates or macros that can extract the repetition in the same way that procedures can extract simple repetition.

                                                  1. 1

                                                    Thanks for the response. I think you’re using a pretty esoteric definition of “pattern”. Also, DRY isn’t really about literal repetition of code, it’s about sources of truth in the information sense. It’s often far better to leave repetition alone than to try and abstract it away!

                                              3. 2

                                                Huh! I’ve never heard this perspective before. I’ve always understood design patterns as explicitly proscriptive: they originate from repetition in practice, yes, true — but they exist as well-defined things, serving as a kind of shared domain language for practitioners. I’ve always liked the notion that design patterns are “cages for complexity” — things to actively apply to problems to keep them tractable.

                                            1. 6

                                              It’s interesting comparing this approach to C++. In C++, you’d generally write code that parameterizes over the allocator at compile time using a template parameter. This gives you the flexibility of libraries that work with many different allocators, but without any runtime overhead, since the dispatch is selected at compile time.

                                              I’m interested in Zig, but haven’t really dug into it. Anyone know why they chose a runtime approach?

                                              1. 3

                                                In C++, you’d generally write code that parameterizes over the allocator at compile time using a template parameter.

                                                I haven’t written C++ in over 20 years, so forgive my ignorance here: does this apply to the STL too?

                                                One of the big ergonomic points for Zig is that you know when allocation is happening because you passed an allocator to a function, and everything that can fail allocation returns an error that must be dealt with at the return site, which has advantages (and disadvantages) over exceptions on out of memory errors.

                                                1. 3

                                                  It does. It’s usually an optional type parameter with a default.

                                                  1. 3

                                                    Could this ergonomics/functionality also be implemented in Zig via compile-time monomorphisation over allocators?

                                                    1. 3

                                                      It does, and it’s why people who want custom allocators don’t use the STL.

                                                      If you pass it as a template arg you can only have one of each kind of allocator, so you can’t really do anything beyond a malloc/free wrapper. It also means any code using such data structures has to be a template too and that brings its own problems.

                                                      edit: I guess template <typename Allocator> f(Allocator* a); works, but making everything templates means you have to put everything in headers and then those headers have to include more headers and it becomes a big mess. All for something which is unlikely to ever matter in the end and can be worked around if it does.

                                                  1. 35

                                                    I have never used Zig and this will affect me in absolutely no way.

                                                    But this is such a marvelous piece of technical writing that my immediate reaction was to try to read more from the author, only to be saddened to learn that this is the only thing that they have published.

                                                    1. 6

                                                      Agreed, totally. This is just really beautifully lucid writing.

                                                    1. 2

                                                      Aspirationally:

                                                      Going to the aquarium with some friends. Socializing in person still feels weird. It’s hard to get that back into my comfort zone.

                                                      Working some more on a track in Ableton Live. Most of the pieces are there now, it’s mostly a matter or rearranging some stuff and getting the overall flow there.

                                                      Maybe working a little more on my fantasy console project.

                                                      1. 3

                                                        Continue working on a typeface I’ve been designing for a year now: https://neil.computer/notes/introducing-berkeley-mono/

                                                        Feedback is most welcome. The ‘r’ glyph is wonky, there are issues with the way uppercase ‘W’ looks, especially in small sizes.

                                                        1. 2

                                                          I really like it! You should put up a full alphabet if you have one. Also I see you have Ø and Å, but I don’t see Æ/æ, or AE. They’re all part of the Norwegian alphabet.

                                                          The example images have pretty low resolution, enough to make it look blurry on a “default scaled” 1440p 27”. Makes it a little hard to judge.

                                                          1. 1

                                                            Looks great. I love the shape of the circles. Strong Eurostile vibes, which I don’t recall seeing in a monospace font.

                                                            I agree on the “r”. The serif on top seems too angular and not fitting with the rest of the font. I couldn’t find an uppercase “W”, but the lowercase looks good to me.

                                                            1. 1

                                                              I really like it! I also appreciate the use of Univers on your site

                                                            1. 6

                                                              Isolating in my office, as I’ve picked up the dreaded lurgy. Probably some mixture of video games, reading, messing about on my server and watching movies. (The thing I’m most annoyed about is by the time I’m out of isolation, cinema’s will have stopped showing the new 007 movie. Probably serves me right for leaving it as late as possible.)

                                                              Last weekend I managed to get off-site backups setup for my NAS. This weekend my aim is to make the media side of the homelab more resilient as I’m bored of restarting Plex / Sonarr weekly because someone in the house has moaned they are unavailable.

                                                              1. 4

                                                                I hope you feel better soon.

                                                              1. 25

                                                                A quick, rough timeline:

                                                                • 2005: git is released, and Junio Hemano becomes the core maintainer
                                                                • 2008: the first thread about staging on the linked article, and, GitHub is formed
                                                                • 2021: 13 years later, this is still a thing

                                                                There’s something about “we can’t change this, what about all the people using this” in the early days, becoming an issue for far far longer and for far many more people, that feels like a failure mode.

                                                                1. 31

                                                                  I’m reminded of the anecdote about make: Its original author used tab characters for indentation in Makefiles without much thought. Later when they decided to add make to UNIX, they wanted to change the syntax to something more robust. But they were afraid of breaking the ten or so Makefiles in existence so they stuck with the problematic tab syntax that continues to plague us today.

                                                                  1. 12

                                                                    Your comment reminds me of the origin of C’s confusing operator precedence rules:

                                                                    Eric Lippert’s blog post “Hundred year mistakes”

                                                                    There were several hundred kilobytes of existing C source code in the world at the time. SEVERAL HUNDRED KB. What if you made this change to the compiler and failed to update one of the & to &&, and made an existing program wrong via a precedence error? That’s a potentially disastrous breaking change. …

                                                                    So Ritchie maintained backwards compatibility forever and made the precedence order &&, &, ==, effectively adding a little bomb to C that goes off every time someone treats & as though it parses like +, in order to maintain backwards compatibility with a version of C that only a handful of people ever used.

                                                                    But wait, it gets worse.

                                                                    1. 18

                                                                      I think this article includes a logical fallacy. It assumes that whatever you’re doing will be successful, and because it is successful, it will grow over time. Since it will grow over time, the best time for breaking changes is ASAP.

                                                                      What this logic ignores is that any tool that embraces breaking changes constantly will not be successful, and will not grow over time. It is specifically because C code doesn’t need continual reworking that C has become lingua franca, and because of that success, we can comment on mistakes made 40+ years ago.

                                                                      1. 6

                                                                        Sure, but this error propagated all the way into Javascript.

                                                                        I’m not saying C should have changed it. (Though it should.) But people should definitely not have blindly copied it afterwards.

                                                                    2. 3

                                                                      I’m curious why you think it is problematic? Just don’t like significant whitespace? But make also has significant newlines…

                                                                      1. 4

                                                                        For me it’s because most editors I’ve used (thinking Vim and VSCode) share their tab configs across file types by default.

                                                                        So if I have soft tabs enabled, suddenly Make is complaining about syntax errors and the file looks identical to if it was correct. Not very beginner friendly.

                                                                        1. 4

                                                                          IIRC Vim automatically will set hardtabs in Makefiles for you. So it shouldn’t be a problem, at least there (as long as you have filetype plugin on).

                                                                          1. 2

                                                                            I always make sure to have my editor show me if there are spaces at the front of a line. Having leading spaces look the same as r trb is a terrible UX default most unfortunately have

                                                                        2. 2

                                                                          Thanks, I hate it

                                                                          1. 1

                                                                            What was the problem with tab syntax?

                                                                        1. 4

                                                                          I’d like to write something book-like someday, and I’d idly mused about doing print on demand. This has convinced me that I actually don’t want to do that.

                                                                          This print work is fascinating and valuable and hard, and I don’t want to do it. I want to git push book readers_brain.

                                                                          1. 19

                                                                            Author here. You can definitely do a print edition of a book much easier than I did. I specifically chose to:

                                                                            • Hand illustrate everything.
                                                                            • Use a two-column design with lots of asides.
                                                                            • Include every single line of code in the book along with contextual location information so that readers can build up a complete program.

                                                                            If your technical book doesn’t do all that, it’s a lot easier. A lot of CS books are done in LaTeX where the process is mostly automated. It is still a decent amount of work to edit, find a copyeditor, index, design a cover, etc. If you don’t have any graphic design background, you’ll end up with a more beautiful book if you go with a conventional publisher.

                                                                            1. 7

                                                                              The asides in the margins remind me of Tufte’s books. Did you take any influence from them?

                                                                              I think I remember reading something about how he also made sure that every diagram and the text that referred to it appears on the same page? And it involves a ton of labor. Not sure if that is true but I think so.

                                                                              I’m very impressed by the craftsmanship! :)

                                                                              1. 5

                                                                                Oh, yes, I have been a Tufte fanboy for many years. :)

                                                                              2. 4

                                                                                Thanks for the reply! All of that work definitely comes across, I wish my CS textbooks looked as good as the pictures you featured.

                                                                                Side thing: I’m totally bookmarking your tile_pages script. That kind of non-text document-modification-verification tool seems super useful.

                                                                              3. 6

                                                                                I used a professional publisher, but did all of the typesetting of my books myself using LaTeX. The only difficult part was making the copyright page correspond to the publisher’s template. I wrote in a semantic markup language that happened to be valid LaTeX syntax, so for the ePub editions, I wrote a small program that parsed this into a DOM-like structure and emitted semantic HTML. I would probably just use something like PanDoc and Sile if I did this again. I did all of the diagrams with OmniGraffle (sadly, my favourite drawing program is an older version of OmniGraffle that doesn’t run on modern macOS - Omni Group went full hipster and turned their beautifully clean NeXT-inspired UIs that were a joy to use into things that looked great while posing with a MacBook in a coffee shop and are a pain to use).

                                                                                I spent a while tinkering with the typeset output because I’m a typography geek, but it wasn’t really required. I used URW Grotesk for the titles and the standard LaTeX fonts for pretty much everything else.

                                                                                Getting a good copyeditor is probably the hardest part if you’re self publishing. I worked with some fantastic ones and some mediocre ones, even with a professional publisher in the mix.

                                                                                640 pages over 15 months is a very nice sedate rate, it averages to less than 1.5 pages per day. That’s a nice sustainable pace.

                                                                                1. 6

                                                                                  I’m writing a book about publishing for technical people and the interior formatting has almost been the doom of the project because I wanted an index in the print edition. As munificent says, there are much easier ways to do it depending on your particular priorities.

                                                                                  My chapter about interior formatting has a recommendation specifically geared toward technical people: AsciiDoc. The format provides a good deal of power, and the tooling is pretty good — but a lot depends on what you want your end product to look like. Crafting Interpreters is a beautiful work and there’s no easy way to get that kind of format without the work (other than hiring that work out, either to a traditional publisher or independently).

                                                                                  Leanpub also does a good job of giving you tooling to target ebook and print.

                                                                                  1. 2

                                                                                    I’m probably going to use leanpub, primarily because I have no idea what I’d want a book too look like. I just want it to be readable.

                                                                                    Ironic that a book about publishing has troubles in the designing/publishing pipeline!

                                                                                    1. 4

                                                                                      I just want it to be readable.

                                                                                      You can try pandoc. I use it to convert GitHub style markdown to pdf/epub. The default conversion by pandoc is good enough, but I spent some time customizing it. There are templates made by others that you can use too. That said, I haven’t done print versions, only ebooks.

                                                                                      I wrote a blog post about my set up (includes other resources at the end too): https://learnbyexample.github.io/customizing-pandoc/

                                                                                      1. 3

                                                                                        Part of the value of the book is that I’m blazing the trail somewhat.

                                                                                        There are actually a number of fine choices for people who don’t care about having an index, and there’s even a program that will add an index to your PDF file. So it’s not terrible in general. I’ve published 4 novels and that experience was quite pleasant!

                                                                                  1. 3

                                                                                    Aside from a couple of IEEE pecadillos, almost all of these are from implicit conversions. The reason JS has so many implicit conversions was because the initial design of the language had no way to report runtime errors. In the absence of that, every operation on every possible kinds of operands had to produce some kind of value and now here we are.

                                                                                    If there a lesson here for other language designers, it’s that you need to have a coherent strategy for handling type errors from day one or you will end up regretting it. That can be static types, exceptions, or something else, but you have to think about it and do something or you’ll end up having to fill every single cell of your implicit conversion table with increasingly arbitrary values and your users will hate you forever.

                                                                                    1. 9

                                                                                      Use the right tool for the right job.

                                                                                      1. 30

                                                                                        Really not a fan of this quote. How do you know what the right tool is? How do you know what the right job is? “Use the right tool for the right job” is like saying “write good code, not bad code.” It doesn’t inform us.

                                                                                        1. 17

                                                                                          The phase is best used among the already informed when making a decision about what is available. To the uninformed I would simply say “do not be dogmatic.”

                                                                                          1. 6

                                                                                            I agree that this quote can be frustrating and has the property that its wisdom is most visible in hindsight.

                                                                                            But the way I interpret this is not to mean “You should know the right tool”, it’s to draw attention to the fact that the right tool is context sensitive. “The right job” part of the quote is critical. It’s saying that a blanket answer doesn’t work because the solution depends on at least one parameter. Seek out those parameters and then the solution becomes more obvious.

                                                                                            1. 4

                                                                                              How do you know what the right tool is? How do you know what the right job is?

                                                                                              You know what the right tool is by getting good at lots of different tools, thinking about them a lot, reading what people have written, and talking with people about what they have experienced. You know what the right job is basically via the same method.

                                                                                              As c12 has pointed out, when you rely too much on other people’s experiences, or those experiences are not varied enough, you can fall into the trap of dogma. If you rely too much on your own experiences, without considering the wisdom of others, you can fall into the trap of egotism.

                                                                                              I like this quote a lot because it’s what broke me of my antipattern of looking for the Perfect Programming Language. The smartest person I knew was doing a game jam and writing the game in C, and I asked why they would use something so backwards instead of Haskell or OCaml or Lisp or anything else. Their response was that C was a good tool for what they were trying to do.

                                                                                              1. 2

                                                                                                Could not agree more. In addition to your questions: Should you choose the perfect-fit tool that you’ve never used before or the fits-ok tool that you’re an expert in? What about the perfect tool that you’re an expert in but no one else on the team has used before? And if we’re just talking about language paradigms in a code base, how do you prioritize problem-paradigm fit versus consistency?

                                                                                            1. 13

                                                                                              Very impressive. But is it just me, or is guys buying a lot of clothes? 15 pair of shoes over the course of three years? That sounds like a lot.

                                                                                              I did a quick count and I have used 8 shoes over the course of three years.

                                                                                              1. 7

                                                                                                Not judging but this guy has more white sneakers than I have shoes, and more belts than I have pants.

                                                                                                1. 6

                                                                                                  If you’re the sort of person who would track every item of clothing you wear, every day, for three years, you probably already like fashion!

                                                                                                  1. 5

                                                                                                    If he’s an avid runner it could be running shoes.

                                                                                                    1. 6

                                                                                                      In the article, he says sportswear is excluded.

                                                                                                      1. 2

                                                                                                        This. I know I can go through three pairs about every thousand miles, where I’m averaging about 1.5k-mi every year.

                                                                                                        1. 2

                                                                                                          Looking at the pictures in the post, it doesn’t look like very many running shoes.

                                                                                                          1. 2

                                                                                                            I did a quick inventory:

                                                                                                            • Dress shoes that I only use indoors at work (12 years old), looks like new. But they go to the shop every couple of years.
                                                                                                            • Winter boots (~10 years)
                                                                                                            • Winter boots (~3 years)
                                                                                                            • Chelsea boots (~6 years)
                                                                                                            • Sneakers (~ 3 years old)
                                                                                                            • Sneakers (~ 2 years old)
                                                                                                            • Running shoes (~ 5 years)
                                                                                                            • Indoor training shoes (~6 years old)
                                                                                                            • Dress shoes (~7 years old)

                                                                                                            Then I have a couple of shoes that I have not used for years.

                                                                                                            Hmm.

                                                                                                          2. 3

                                                                                                            His GitHubIO page is actually really interesting in this regard: https://hoverfalt.github.io/shoes.html

                                                                                                            1. 2

                                                                                                              The author probably lives here in Finland (Reaktor is a mostly-Finnish company). You need shoes for temperatures from -25 to +25 degrees Celsius, for snowy/slushy/wet/dry conditions. If you do sports year round or like variety, that quickly adds up.

                                                                                                              EDIT: on a closer look, yeah, I have less than half the amount of clothes of the author.

                                                                                                              1. 3

                                                                                                                Yes. I live in the middle of Sweden. So have more or less the same climate. Very seldom -25, but still.

                                                                                                              2. 1

                                                                                                                It’s a lot of clothes, but it makes more sense if you look at it as a hobby.

                                                                                                              1. 2

                                                                                                                I like most of this except the rule against early returns. Consider the suggested alternative:

                                                                                                                if (input less than waterleve) {
                                                                                                                    turn motor on
                                                                                                                } else {
                                                                                                                    turn motor off
                                                                                                                }
                                                                                                                

                                                                                                                When reading this code, you ask yourself “when can the motor be turned off”? To answer that, you need to reason about the control flow that can enable that statement to be reached. You have to look up at the if condition. When that condition is true, the then block is run. Then you negate the condition in your head and understand that the else can only be reached when the condition is false. That’s exactly the same mental processing you do for an early return.

                                                                                                                In particular, consider a more complex example using early returns:

                                                                                                                if (file does not exist) return file not found error;
                                                                                                                
                                                                                                                if (file is empty) return file empty error;
                                                                                                                
                                                                                                                if (file cannot be read) return read error;
                                                                                                                
                                                                                                                read file;
                                                                                                                

                                                                                                                The author argues that this is better written as:

                                                                                                                if (file does not exist) {
                                                                                                                  return file not found error
                                                                                                                } else if (file is empty) {
                                                                                                                  return file empty error
                                                                                                                } else if (file cannot be read) {
                                                                                                                  return read error;
                                                                                                                } else {
                                                                                                                  read file;
                                                                                                                }
                                                                                                                

                                                                                                                But, again, ask yourself, “When does the file get read?” The answer to that is “When the file exists, and it’s not empty, and it can be read.” Even though there are technically no “early returns”, the chain of else if clauses still means that you have to maintain all of those if conditions in your head when reasoning about the control flow. The underlying control flow is the same, as is the cognitive load.

                                                                                                                Also, for what it’s worth, I think Dijstra’s “Goto Considered Harmful” letter to be a weak, poorly-argued claim and Dijkstra’s own “guarded control flow” language is just as bad as goto according to his own logic.

                                                                                                                1. 1

                                                                                                                  We have to remember all those conditions in both cases, but the second one makes it much more easier for us to to identify what they are and to separate them from the rest of our code.

                                                                                                                  1. 1

                                                                                                                    the second one makes it much more easier for us to to identify what they are

                                                                                                                    Do you have evidence to support that claim?

                                                                                                                    1. 1

                                                                                                                      Well, in the first case, they (the conditions) may be scattered all over the function, while in the second one they are all contained in this one if-else clause. Consider an example where the read file; operation is 100 lines of code long - if an early return is permitted, then you would have to read all those 100 lines in order to ensure that they don’t contain a return statement, while if it’s not, then all of those would be in one big else block, and you would know they do not contain extra conditions.

                                                                                                                1. 1

                                                                                                                  Short history of the if statement? Was McCarthy exagerating when he claimed to invent it in 1960?

                                                                                                                  https://en.wikipedia.org/wiki/McCarthy_Formalism#McCarthy's_notion_of_conditional_expression

                                                                                                                  1. 2

                                                                                                                    Fortran (well, FORTRAN at the time, I guess) had conditional code execution. So it could jump from one place to another based on some Boolean expression. This was a mechanistic, imperative thing.

                                                                                                                    McCarthy invented the conditional expression. It was a way in mathematics of defining recursive functions that didn’t have an infinite regress. It also just happened to turn out that you could take McCarthy’s math and make a computer implement it. It’s not clear to me how much innovation McCarthy’s conditional expression really is. It seems conceptually quite similar to piecewise functions, which I’m guessing are much older.

                                                                                                                    1. 1

                                                                                                                      The if statement existed in FORTRAN, I think, but the blocks were a later idea. The first language that had blocks was ALGOL 60. And yes, McCarthny was involved.

                                                                                                                      http://www-formal.stanford.edu/jmc/recursive.pdf (see footnote 2).

                                                                                                                      https://craftofcoding.wordpress.com/2017/04/29/the-evolution-of-if-i-from-fortran-i-to-algol-60/

                                                                                                                    1. 1

                                                                                                                      It was an audacious goal, especially for a language released in 1995.

                                                                                                                      Is it just the baggage that’s implied? Or that newer things are necessarily better/faster?

                                                                                                                      1. 25

                                                                                                                        Maybe the implication is that the language has already been incrementally improving for 25 years, so a 3x speed-up at this point in its development would be quite remarkable.

                                                                                                                        1. 4

                                                                                                                          The last time I looked at Ruby, it was significantly slower than Squeak, which at the time was a fairly faithful reimplementation of a Blue Book Smalltalk-80 bytecode interpreter and what I’d consider to be minimal baseline performance for a new dynamic language implementation that learned anything from prior work. I believe Squeak has since added JITs and other more modern features but at the time it was focused aggressively on being easy to understand and hack on at the expense of performance. Being slower than Smalltalk when your language is Smalltalk minus several of the bits of Smalltalk that are hardest to implement efficiently was pretty depressing.

                                                                                                                          There was definitely a lot of room for improvement.

                                                                                                                          1. 3

                                                                                                                            I think it’s worth pointing out that Ruby is a much larger language with a huge core library. Surface area matters a lot when it comes to optimizing language performance. A typical Ruby application will touch a lot of language features, so if any part is left unoptimized, that loss may easily dwarf the gains made in other areas. You have to optimize the whole enchilada, and that can be cost-prohibitive for teams without deep pockets.

                                                                                                                            I can’t find the links now, but I recall one of the people on PyPy saying Python is harder to optimize than JS just because Python’s core lib surface area is so large. Mike Pall said something similar about why LuaJIT was feasible as a mainly single-person project: the language and libs are tiny.

                                                                                                                        2. 1

                                                                                                                          Maybe they are saying older things are faster because they were designed for slower computers, making it hard improve on performance?

                                                                                                                        1. 9

                                                                                                                          Very interesting read. I wonder how much the bad times with async the author met, are caused by the 2000’s style of async that is based on callback hell. Modern async (as implemented in, say, rust) emphasizes writing async functions in a direct style (with “async”/“await” keywords and compiler support to turn these into state machines, not callbacks) that should be as readable as their purely sequential counterpart. The dispatch might also be different, using a work-stealing scheduler backed by a pool of threads, instead of many single-threaded queues of events.

                                                                                                                          1. 14

                                                                                                                            I don’t think it fundamentally changes things. async/await is more syntax than anything, and will not prevent ordering bugs because of concurrent executions. It also doesn’t change the fact that async code is contagious, when something deep down your stack suddenly becomes async, you have to mark every caller as async as well. Also, I have some doubts about the argument that “with a good enough scheduler, everything can be made async”. I do think it’s still worth carefully considering whether a function should be async. There is probably a good balance to find, and a lot of measurements to do.

                                                                                                                            1. 4

                                                                                                                              Async/await is more than syntax. It enables stackless coroutines, which make more efficient use of threads.

                                                                                                                              Part of the problem with dispatch_async is that it’s quite expensive, since it requires creating a heap-allocated block. The first time I tried using it instead of locks to make a class thread-safe, I (fortunately) did some comparative profiling and backed out that change in a hurry.

                                                                                                                              Contagion is real, but I think it’s less of an issue when there’s much less runtime and syntactic overhead to making a function async.

                                                                                                                              1. 7

                                                                                                                                it requires creating a heap-allocated block

                                                                                                                                So does async/await in most implementations I’ve seen. You still have local variables. If you’re going to have stackless coroutines, those variables need to exist somewhere in memory, right? That somewhere is on the heap.

                                                                                                                                1. 4

                                                                                                                                  Afaik in rust you only allocate on the heap once for a full “task” (comprising many futures, which are translated by the compiler into an enum representing the state of a state machine). So you don’t have one allocation per future + one allocation per callback on that future. https://aturon.github.io/blog/2016/08/11/futures/ and following posts explain it much better than I can hope to do myself.

                                                                                                                                  1. 3

                                                                                                                                    Dammit, you’re right. I’d overlooked that. (But c-cube’s reply implies this can be optimized a lot, which is good.)

                                                                                                                                    1. 4

                                                                                                                                      In most cases, the optimizations still don’t make up for the high costs associated with distributing work to executor threads, handling communication around scheduling, and stitching results back together. This is well illustrated with the Universal Scalability Law. async is a very sharp tool that only has a couple use cases where the performance actually is improved in Rust, but the downsides are always severe in terms of additional bug classes related to accidental blocking, additional compiler errors due to requiring anything in-scope to be Send if it survives across a suspension point, additional compilation time as large trees of async-specific dependencies are pulled in, etc.. etc… etc… it really eats into the amount of energy that engineers have left to actually be productive, and as a result the async Rust sub-ecosystem suffers from significant performance and quality issues that simply can’t compete with non-async implementations that are not hampered by these costs during their development.

                                                                                                                                      Like many things in computing, it’s not popular because it’s good, it retains users because of the social connections people form while compensating for its badness. As humans, we avoid acquiring information that could cause us to lose contact with a social group which manifests as async users either never comparing performance to threads or acting like they hadn’t seen any information at all when their tests show them that async makes things worse.

                                                                                                                                  2. 3

                                                                                                                                    Contagion is real, but I think it’s less of an issue when there’s much less runtime and syntactic overhead to making a function async.

                                                                                                                                    Isn’t the async-contagion cost the issue of “two versions of everthing”? (What colour is my function)

                                                                                                                                    Isn’t this the big idea of golang?

                                                                                                                                    There is no async contagion. But you get the benefits.

                                                                                                                                    Coroutines are managed by passing first class objects around (channels), which are lexically scoped so you know which routine is yielding to which.

                                                                                                                                    OS-level blocking is managed by the runtime so you pretend to write straight-through code but if you call read() then things are managed so that you don’t block anything else.

                                                                                                                                    1. 5

                                                                                                                                      Goroutines require custom stack management, which has a high cost in complexity — it took the team many years and two entirely different implementations to fix performance problems. The custom stacks also create interoperability problems with other languages, since the stacks have unusual behaviors like growing on demand and relocating(!). It also creates problems calling out into other-language code that can block, since you don’t want to block the goroutine scheduler.

                                                                                                                                      Also, all those stacks increase memory usage. Zillions of goroutines doing a task are potentially carrying around a zillion deep stacks.

                                                                                                                                      Channels are a nice feature but they’re really awkward to use for coroutine / actor implementations; I’ve tried. You have to write a lot of boilerplate to serialize and deserialize the messages, and it ends up being pretty expensive.

                                                                                                                                      1. 2

                                                                                                                                        Do you know of any good articles about how the Go project solved these issues, and what they were, exactly?

                                                                                                                                        1. 2

                                                                                                                                          Something’s here: https://docs.google.com/document/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/pub

                                                                                                                                          via: https://github.com/golang/go/wiki/DesignDocuments (Contiguous Stacks)

                                                                                                                                          for newer design docs, see also: https://github.com/golang/proposal

                                                                                                                                          Personally I’m not tracking the proposals since long ago, so I don’t have any idea if more happened since the doc I linked first above, which was around Go 1.3…

                                                                                                                                          1. 1

                                                                                                                                            Thanks for the links! Definitely worth a read

                                                                                                                                        2. 1

                                                                                                                                          Also, all those stacks increase memory usage. Zillions of goroutines doing a task are potentially carrying around a zillion deep stacks.

                                                                                                                                          I thought this was again a selling pt of golang. goroutines are generally cheap?

                                                                                                                                          https://tpaschalis.github.io/goroutines-size/

                                                                                                                                          On a mid-end laptop, I’m able to launch 50 million goroutines.

                                                                                                                                          It may be possible to arrange things so that an app has many deep stacks, but it’s not a case I’ve heard people coming across in practice.

                                                                                                                                          Channels are a nice feature but they’re really awkward to use for coroutine / actor implementations; I’ve tried. You have to write a lot of boilerplate to serialize and deserialize the messages, and it ends up being pretty expensive.

                                                                                                                                          I agree channels are cumbersome. I think this is likely to be due to the lack of higher level helpers, which I hope/expect will be addressed once go2 lands generics.

                                                                                                                                          Having routines to handle fan-out, actor idiom etc genericised on “chan T” should help a lot with channel ergonomics I think.

                                                                                                                                          It also creates problems calling out into other-language code that can block, since you don’t want to block the goroutine scheduler.

                                                                                                                                          That’s an argument for all languages, including golang. If you block a thread in another language, the golang scheduler can’t help you. But that kind of proves the point - in golang, that’s a real win?

                                                                                                                                        3. 2

                                                                                                                                          Java’s Project Loom is another try at providing concurrency and parallelism without dividing the world in two. Time will tell if it succeeds!

                                                                                                                                  1. 3

                                                                                                                                    Definitely:

                                                                                                                                    • Continue working through the last editing pass of “Crafting Interpreters” before I typeset it. It’s moving along fairly quickly, though it’s still a lot of text to grind through. Should have written a shorter book!

                                                                                                                                    • Writing postcards for “Reclaim Our Vote” to encourage people who might have been disenfranchised to be able to vote.

                                                                                                                                    Aspirationally:

                                                                                                                                    • Enjoying the hopefully smoke-free cool air after last week’s utterly miserable hot/muggy/smoky weather.

                                                                                                                                    • If I find the time, assembling the Electrosmith 3340 VCO DIY kit I got last week.

                                                                                                                                    • Maybe noodling on the FM synth for a fantasy console I started tinkering on again.

                                                                                                                                    • Watching TV with the wife. Currently Sampson Boat Co’s YouTube series rebuilding Tally-Ho (amazing and very good for my mental health) and “Lovecraft Country” (good but sometimes a little heavy and too relevant given 2020 context).

                                                                                                                                    • Playing Super Smash Bros with the kids. The day my oldest can consistently beat me is the day she graduates to full tween-hood and loses all respect for me. So far that day has yet to arrive.

                                                                                                                                    Mostly, just trying to survive the dumpster fire that is USA 2020 with some level of sanity intact.

                                                                                                                                    1. 2

                                                                                                                                      Excellent. Crush the children. Let them know just what kind of man their father is.

                                                                                                                                      On a lighter note, when will they announce the next damn DLC character?!

                                                                                                                                      1. 1

                                                                                                                                        Oh, no. I didn’t even realize there was DLC for Super Smash Bros.

                                                                                                                                        1. 1

                                                                                                                                          Did I misunderstand and you are playing the n64 original? Or are you playing ultimate? If the later, they are actually on their second DLC. :)

                                                                                                                                          Actually if you were playing the n64 version people are working on putting new characters in it:

                                                                                                                                          http://n64vault.com/ssb-characters:smash-remix

                                                                                                                                          They apparently released Bowser recently:

                                                                                                                                          https://youtu.be/CvXZ6d9aUUg

                                                                                                                                          1. 2

                                                                                                                                            I’m playing Ultimate on the Switch. I just try to not pay attention to stuff like DLC.