7 ms·
So how do you know that your low-level construct is on the path to where you want to be? Quite often, you can see immediately that you need to represent X, tha
by mannykannot 9y ago
So how do you know that your low-level construct is on the path to where you want to be?
Quite often, you can see immediately that you need to represent X, that representing X requires foo() and bar(), and you can confidently make these things with a reasonable expectation of their usefulness. This already depends, however, on having done some sort of preliminary high-level design thinking. How much you need to do to be confident that you can identify an efficient set of primitives depends on what you are trying to do and how familiar you are with its problems. Writing primitives and utilities can simply be a way to avoid tackling the difficult bits.
For a cautionary tale providing a counterpoint to this article, there is this somewhat well-known case involving writing a Sudoku solver:
http://ravimohan.blogspot.in/2007/04/learning-from-sudoku-solvers.html http://ravimohan.blogspot.in/2007/04/learning-from-sudoku-so...
- dang 9y agoThe best way I know is to delay introducing the new abstraction until you have enough material to justify it. This means tolerating more repetition than you want the program to have in the long run, but only temporarily, while you build up a repertoire of cases that can eventually be abstracted away. You wait for the code to tell you how it 'wants' to become shorter, rather than trying to decide the primitives in advance. It's still just a bet that an abstraction will prove useful—i.e. will allow you to write future cases without thinking—and it still might not work out, but this puts the odds in your favor. It works the other way around too: when an abstraction such as a Lisp macro turns out not to be a good fit, simply inline all its usages and delete the macro. This makes the program more repetitive, but only temporarily, while you wait to spot a different abstraction that fits the material better. This again is listening to how the code 'wants' to be: if you get resistance (increased complexity) while moving in a certain direction, backtrack. I try to give every concrete case veto power over a new abstraction. That is, if I've got six cases of a pattern in my code, and five can nicely be shortened with a macro but one is stubborn and refuses, I tend not to write that macro. I used to, but have observed that such abstractions tend to need undoing later. Instead, I'll hold off, tolerate temporary bloat, and wait for more cases. Perhaps when there are nine or ten, I'll get a new idea for a language construct that can express all of them nicely, and then I'll add that construct in the way that pg describes. It's like the Tao Te Ching says: "If you want to shrink something, you must first allow it to expand" [1]. When you hit on a good way to compress code, it feels like a satisfying click [2]. With such an approach, the program seems to breathe: expanding a little and contracting a little, but never straying too far from simple overall. Lisps are the best medium I know of for this. You get finer and more immediate feedback from the material of the code: less is consumed by the noise of the language itself, so it can be adapted to fit the problem better. Your brain fills up more with the dance between problem and solution instead of the externalities of the tools. And you have a wider range of tools for actually working the adaptation. The result feels more like molding clay with your hands than carving bricks with a chisel. 1. http://taotechingme.com/chapter-36-if-you-want-to-shrink-something-you-must-first-allow-it-to-expand http://taotechingme.com/chapter-36-if-you-want-to-shrink-som... 2. https://www.npr.org/2010/12/30/132488837/The-Behind-The-Scenes-Partnership-At-Apple https://www.npr.org/2010/12/30/132488837/The-Behind-The-Scen...
- kaseyb002 9y agoThere is an interesting parallel here between bottom-up programming and the common law system. Common-law courts don't create law in advance; they wait for conflicts to arise. After enough similar conflicts arise and cases have been decided, an abstract "law" organically emerges that is used to decide future similar conflicts.
- mbrock 9y agoIt's also kind of just like concept formation and theory building in science. In algebraically or mathematically oriented communities like much of the Haskell world, you see a similar kind of "bottom-up programming" but with an added focus on compositional structures with laws for reasoning. When a Haskell module really comes together beautifully, it's because you've managed to extract useful standalone concepts that combine in an algebraic way, with binary operators, associativity, and so on. Eric Evans in Domain-Driven Design, usually considered a book about object orientation, also comes to a conclusion that if most of a program's logic can be expressed in pure functions and closed operations on value objects, then you can attain a "supple design." I suppose the Lisp community has concepts like "value abstractions and syntactic abstractions" or something like that, one of them being related to well-suited data types, and the other being related to macros. Incidentally, I've recently come to realize that Agda, the dependently-typed Haskell-style language, comes very close to being Lisp-like in its support for syntactic abstractions: you can go a long way with just custom mixfix operators (e.g., defining if_then_else_)... and then there's also a sophisticated macro system, using an AST data type and a "typechecker monad." It's a lot of fun!
- Pimpus 9y agoThat reminds me of a blog post[0] I once read by Casey. It does a great job of capturing my thoughts on the subject: in short, that premature abstraction is evil and a productivity killer. Many programmers fall into the trap because of how tempting it is to try and make everything neat and tidy, despite the costs in programmer productivity, program performance, and code maintainability. [0]: https://caseymuratori.com/blog_0015 https://caseymuratori.com/blog_0015
- narag 9y agoSo how do you know that your low-level construct is on the path to where you want to be? It's kind of growing the grass and let people walk on it, then decide where to put slabs on the paths. Anyway I bet you are considering the cost of making changes. That cost is different for different languages.
- montrose 9y agoI find it's a valuable heuristic simply to try to make your code short (in tokens, of course, not characters). That rule means that it's worth defining a new operator if you use it enough to save more than the amount of code you need to define it. There are exceptions, of course. I have a higher standard for macros. But overall the principle of optimizing for brevity has held up through decades of programming of various types.
- geocar 9y ago> I find it's a valuable heuristic simply to try to make your code short (in tokens, of course, not characters). Make it short in characters as well. Fitting more code on the screen makes it possible to see common subsequences and methods of abstraction not otherwise possible. It has dramatically improved my programming, and I recommend anyone who can to spend 6-8 months practicing it. For more on the subject: - https://news.ycombinator.com/item?id=13571159 https://news.ycombinator.com/item?id=13571159 - https://news.ycombinator.com/item?id=10872782 https://news.ycombinator.com/item?id=10872782 - http://archive.vector.org.uk/art10500700 http://archive.vector.org.uk/art10500700
- tom_mellior 9y agoI disagree. I recently read through Real World Haskell (http://book.realworldhaskell.org/read/ http://book.realworldhaskell.org/read/ though it seems down right now?) as a refresher, and it devotes a lot of time to examples of the form "here is a three-line function, but aargh, it is too long, let's turn it into a two-line function by peppering it with <$> and <*> and <@!^!@> operators!". The savings in code size is small, but in every single case I found their "shorter", "simpler" program harder to read.
- tome 9y agoI suspect that's a pedagogical device to motivate introducing a style that can make code shorter and more readable, not a prescription to always convert three lines to two by using sigils.
- 9y ago