Need help?
<- Back

Comments (55)

  • getnormality
    Strong resonance with the famous essay "The Rise of Worse is Better" [1], which contrasted the (better) "MIT/Stanford style of design" with the (worse) "New Jersey approach".MIT/Stanford:> Simplicity -- the design must be simple, both in implementation and interface. It is more important for the interface to be simple than the implementation.New Jersey:> Simplicity -- the design must be simple, both in implementation and interface. It is more important for the implementation to be simple than the interface. Simplicity is the most important consideration in a design.TFA maps "simplicity" to "MIT/Stanford simplicity" (simplicity for the user) and "smallness" to "New Jersey simplicity" (simplicity for the developer).I wonder if the root of the tension between the two schools comes down to the ambiguity of the user/developer distinction. Developers are also users. Simplicity of implementation is helpful to developers when they are working directly on implementation, while simplicity of interface is helpful to developers when they are using other developers' work.[1] https://dreamsongs.com/RiseOfWorseIsBetter.html
  • Snarwin
    > There's no native Unix equivalent to frequencies, this sort | uniq -c is the closest we can get. Not only is it less performant (it has to collect the full input into memory before continuing), but it ties aggregation to ordering.One of the core features of the Unix command-line is that it is user-extensible. If there's no "native" command equivalent to frequencies, you can write your own, and it will be given the same first-class treatment as any other binary in your PATH. This is entirely in keeping with the Unix philosophy of simple implementations.
  • Twey
    > The reason for this is that in Rust, a struct couples type-checking to a fixed data representation. You can't get one without the other.> Clojure decouples data representations from type checking.This is funny to me because seen from the other side, (this) Clojure couples runtime type information to data structures: you're no longer allowed to define a data structure that doesn't have some runtime type information attached. A fixed static structure is just the consequence of not adding dynamic type information.Meanwhile in Rust you can get type-checking ‘without’ a fixed structure by using trait objects.
  • hankbond
    Great piece, very straightforward examples, although I did have to squint for quite a while to grok the Closure portion.I am currently building a piece of very modular software and it has been the hardest-to-design project of my entire career. I would never be allotted this amount of time-effort at any job I have held to make something this robust and clearly defined. Many aspects of this project have taken 3-5 rounds trying-and-trashing to get an abstraction that is uncomplicated.This is precisely why vibe coding is so successful for building tiny isolated scripts, and so disastrous for anything else. It's just really dang hard to build something large and simple.
  • MathMonkeyMan
    This is beside the point, and I'm being pedantic, but the unix pipeline and the clojure expression don't quite do the same thing.The clojure expression reads the entire file into memory first, and then operates on that memory representation.The pipeline processes the input in chunks. The two `sort`s might read the entire contents into memory, but an implementation like GNU sort will instead, for large inputs, create temporary files for sections of the input and then merge sort them to the output.You could make clojure do the same thing, but it might not be as "simple" as the pipeline.
  • embedding-shape
    Important to note as well, is that "simple" isn't "lesser" or "dumber", it can be "more" and "wider", yet still "simpler".Expectedly, Rich Hickey explains it best, watch the "Simple Made Easy" talk if you haven't before, one of the few talks I probably watch bi-yearly: https://www.youtube.com/watch?v=SxdOUGdseq4Few things, concepts and ideas have changed as much of my programming mind as Hickey's talk and ultimately Clojure have done over the years.Wish we had new amazing Hickey talks to link to, maybe it seems he's about the hang up the hammock perhaps?
  • Gehinnn
    Using "length of the correctness statement + length of its proof" works quite well as proxy for complexity of a component (the longer, the more complex).Copy pasted functions with subtle changes mean you cannot reuse the proof (DRY). Giant functions with lots of if/else statements however might cause a branch explosion in the proof. The right abstraction removes lots of assumptions that a proof could depend on, limiting the search space and often forcing elegance (this also applies to math, eg. when reasoning with abstract groups instead of integers). The wrong abstraction might force case distinctions on consumers of the abstraction.
  • zkmon
    Complexity (the opposite of simplicity) has nothing to do with the size of a program, but usually there is a high chance that a large program is more complex than smaller one, purely because the complexity multiplies, not just adds up.A single regex line could be far more complex than a 100-line java program.
  • cat-whisperer
    It's a nice dissection of the topic. But I would like to reframe it as "Simple Is Not 'Always' Small."Logic that the blog presents is sound; but sometimes small works as a good-enough proxy to get to the simplicity that we find elegant.
  • cush
    The author uses the term “coupling” to describe a simple program from the code’s perspective - “you need to have a good mental model of your program”. I agree, but I’d argue that more importantly you need to have a good domain model. Good design communicates accurately and a high Gulf of Evaluation/Execution are actually what makes programs feel “complex” to a user.These concepts simplify for the user these questions: “I know what I want - how do I make the program do it?” (Execution) and “The program did something — what state is it actually in?” (Evaluation)I believe the author was getting at these concepts, especially in their Google Drive example - how the large program has a “small” UX. Understanding the domain model provides a much stronger basis for designing user interfaces, and understanding the Gulf of Evaluation/Execution allows you to build incredibly complex-looking, large UX’s without confusing or overwhelming the user.
  • aghuang
    Simple never means it is easy and it is always the biggest misconception in software.
  • jerf
    Honestly, having watched people argue about what simple is for about the last 10 years, I've pretty much settled on it not being a well-defined term. We know complex when we see it for sure, at least when it is present in quantity, but simplicity is not just the absense of complexity. There's at least three concepts we're all trying to stuff into the same word, and they are not only not "orthogonal" they are often in conflict with each other. I don't even think it can be rehabilitated, it can only really be abandoned, to clear the way to trying to characterize the multiple concepts we're trying to stuff into this one word.It is especially dangerous when something is "good" and people try to appropriate the term to appropriate the goodness of the term, as if goodness flows from a term to the thing it is attached to rather than the other way around. "Simple" is good so my good thing must be "simple" to be "good". But it doesn't. Simple can even be bad, in the wrong place or in the wrong sort of "simple" for a given job.
  • andai
    The word simple is used here a way I'm having trouble wrapping my head around.This specific usage appears to come from this linked talk, Simple Made Easy:https://www.youtube.com/watch?v=SxdOUGdseq4My reaction to the Unix pipeline was that, the reason it exploded in complexity is because the pieces were too simple. They were insufficiently expressive.But the word is used in a different way here, and I'll have to watch the talk to understand what exactly is meant. (Something like orthogonality?)
  • pianopatrick
    I've been thinking about a new AI based dimension to this. If your program is split into smaller decoupled "modules" then all the code for each "module" can fit into an AI context window. In this way the AI can have all the context to edit a "module" by just loading all the code for that "module". You would not need things like vector search as much. If we assume 20 tokens per line of code and the AI context window is 100k to 1M tokens, then that would argue for having "modules" between 5,000 and 50,000 loc, depending on which AI model you are using.
  • anon
    undefined
  • leecommamichael
    I was surprised that the author didn’t translate the piped representation of the first program to a procedural program with explicit calls to subprocedures. It’s bigger, but it’s dead-simple and easy to slip instructions in the middle of.
  • adelks
    "In Clojure this is fairly straightforward"Somehow when things get complex, I could never find fully functional style to be more understandable than imperative
  • rickcarlino
    “When is it useful to be small?”I like this question. Some projects will sacrifice usefulness in the name of simplicity.
  • dasil003
    The first example feels like too much of a straw man, and I'm not sure how I feel about the definition of simple (and yes I've seen Hickey's talk which I very much do agree with). Obviously a cohesive general purpose programming language like clojure is going to do better on a problem with abitrary sub-structure, especially when you want to rethink that substructure. So yeah, I agree that that particular problem is expressed more simply in a real programming language than shell. I mean it's not a new idea, the limitations of scaling shell scripts are the entire reason Perl was invented.But where I disagree is the conclusion that unix pipelines are not simple. IMHO unix pipelines as a platform are incredibly simple and powerful, allowing for solving a massive range of small problems much more elegantly than any general purpose programming language. Obviously the constraints that enable this simplicity at the low-end, are real tradeoffs that prevent simplicity at the high-end. But one of the core principles of effective engineering is do the minimum to solve the problem at hand, no more, no less.
  • aozgaa
    Another solution to the pipeline example, this time making use of a subprogram for the frequency/accumulation: < README.md \ tr -c '[:alpha:]' '\n' \ | tr '[:upper:]' '[:lower:]' \ | awk ' NF { if (!($0 in count)) order[++n] = $0 count[$0]++ } END { for (i = 1; i <= n; i++) { print count[order[i]], order[i] } } ' If you don't allow `awk` in your "pure bash" then ofc this is not satisfactory. But it has the upside that the associative arrays are pretty explicit data structures (for the ordering and counts, respectively).
  • JoachimSchipper
    The general point is true, but the shell pipeline gets a lot more elegant if you use the sort-and-accumulate paradigm that the classic shell utilities were written for (which uses O(1) memory, by sorting on disk). Using mostly the author's own code, and adding --count to uniq: tr < README.md --complement --squeeze-repeats '[:alpha:]' '\n' \ | tr A-Z a-z \ | nl \ | sort --key=2,2 --key=1,1n \ | uniq --skip-fields=1 --count \ | sort --key=2,2n \ | awk '{ print $3, $1 }' (Where the final awk papers over the fact that we're mixing tabs and spaces here; obviously, awk is also good at doing the accumulation step, but uniq --count suffices here.)(I originally posted the above as a comment on lobste.rs, on this same article.)
  • StilesCrisis
    This argument is just based on "I wish the things I need to do were baked into the language." It's nice when that happens, but once programs get sufficiently large and complex, it stops mattering--you're dealing with domain-specific concepts that have zero built-in helpers and you're just building everything yourself regardless.
  • shevy-java
    > Unix pipelines are not simpleBut they are.UNIX Pipes do not mandate having to use tons of different programs with stupid commandline options. I simulate them in ruby itself; method chaining works in a very similar way, but I built a pseudo pipe around it. The idea was more to have an object oriented shell, e. g. combine good ideas from UNIX pipes and the MS powershell.They are simple if you design them well and have them be flexible too. The reason UNIX pipes were awkward is because they delegated onto many different programs such as awk or sed with their own strange rules. Nowhere does it say you HAVE to use such awkward tools. Use better tools and the idea of piping becomes simple, similar to (a more flexible variant of) method chaining. Just without being tied down to a specific object per se (I do use the pipe-handler master object to handle the pipe instructions; each pipe instruct I call cmdlet, e. g. shorter for commandlet, as this is how I like to think about this in terms. This also combines e. g. virtualdub + avisynth ideas. I loved them when I used windows. The idea behind avisynth is great - not necessarily all of the syntax, but the idea that all multimedia audio can be operated on at all times in flexible ways.)
  • drbig
    Strikes a practical chord or two:1. "You need to have taste (so: experience) to do DRY right". Same chunk of code more than once, so natural/expected behavior is to export to a helper... Aaaand the now introduced coupling is (too) often ignored, as in "no thought given whatsoever".2. "Sometimes it's better to just leave it as is". 95% to 98% of "same chunk of code" in a number of places. The temptation to DRY is strong, yet the "numerically mere 2 to 5 pp" make the extracted helper an exercise in all manners of gymnastics. The only correct answer is: do not start.(Own experience; your mileage may vary - if it does, feel free to comment back!)
  • chrisjj
    > In Bash you need a bunch of temp files and ugly opaque regexes, sorts, and joins:> That's because our original program was small but not simple.I would say no - because it was inflexible.
  • voidhorse
    I don't think it's possible to have a universal "colloquial" definition of "simple" that is also precise. It's all relative.This is why I think the formalists studying complexity back in the 50s had the right approach. You can only give "simple" precise meaning within some kind of formal system with a shared set of initial axioms or assumptions. From that point you can define it quantitatively over some set of objects (relations, programs).Funnily enough, this approach also touches on Hickey's etymological derivation. The root words also fundamentally have to do with the quantity of relationships.
  • Anoian
    I mean I am not part of the same company, so I am just talking out of my butt, but I cannot imagine a dev worth their money taking more than two weeks, to fix a bug, especially today with AI assistance, most bugs are found the same day, the gnarly ones maybe take two days and in my lifetime as an engineer (10 years), I have not yet seen a bug that took me more than a week.Taking 9 months to fix a bug sounds alarming to me.
  • jeanmichelselli
    simple = maintainable
  • adelks
    "In Clojure this is straightforward"
  • lilli_put
    [dead]
  • notchdock
    [flagged]