4 ms·
> let forms introducing indentation this is a symptom of using let forms too copiously, rather than using a functional style, where there's very few requiremen
by chii 3y ago
> let forms introducing indentation
this is a symptom of using let forms too copiously, rather than using a functional style, where there's very few requirements to attach names to intermediate arguments being passed between functions.
Often, you would use threading macros (the -> and ->> stuff) to implicitly pass arguments from one function to the next, or define transformations which are themselves just pure functions (so you would define it at the top level).
It's understandable that some interop require a lot of let forms - mainly those that mutate an argument in a chain of procedures (looking at you, java jwt libraries...you know who you are).
- skrebbel 3y agoWell-named intermediary variables often help tremendously with readability though. A language discouraging their use seems like the wrong trade-off decision to me.
- chii 3y agothat's because you have assumed that the code would _need_ such an intermediate variable, which is often the case in imperative code. I argue that if your code _does not need_ this intermediate variable, it makes the code even more readable as it removes more cognitive friction - having fewer things makes it easier to understand.
- Barrin92 3y agothe code doesn't need anything, people reading it do. Names are useful because they make intent explicit. Point free programming in functional languages is often overused. Stuart wrote a good blog post about this a few years ago. "All too often, however, I see the threading macros used in places where they make the code less clear. Early in Clojure’s public existence, there was a fad for “point-free” programming, a kind of code golf without any local variables. This is a fun exercise, but it does not lead to readable code, and I’m relieved that the enthusiasm for this style has worn off. What has remained is a tendency to use threading macros any place they can be made to fit. I think this is a mistake. Rather than making the code easier to read, it makes it harder, forcing the reader to mentally “unwind” the syntax to figure out what it does." https://stuartsierra.com/2018/07/06/threading-with-style https://stuartsierra.com/2018/07/06/threading-with-style
- skrebbel 3y agoIf you name your functions and transducers well, you tell the reader what’s being done to the data. If you name your intermediate data structures well, you tell the reader what the data means. Often, the transformations have generic names such as “unique” or “removeEmpty”, but the variables can get domain-specific names such as “validOrders” or “paidAccounts”. I’m a big fan of designing code in a datastructure-first way. If you get the datastructures right, the code itself is often easy to write and easy to read. This matches FP languages really well (eg immutability), but it doesn’t match long fluent chains/pipes of maps, filters and reduces well at all.
- bcrosby95 3y agoYou can name anonymous functions so your transformations can still have a domain related name associated with it, even if you're using standard library ones.
- skrebbel 3y agoIf you’re making a local anonymous function that you call in one place, just so that a generic function gets a more domain specific name, then you might as well just assign the result to a named variable, right? It’s the same benefit with less indirection.
- BoiledCabbage 3y agoYes those are frequently used in imperative languages, but they aren't really as needed in functional styles as functions end up being smaller and more powerful. The best analogy I can give is if you've used a fluent interface in Java or C# ( like Linq). Take a look at the two larger examples at the bottom of the link. It actually would hurt readability to try to add names for each of those intermediate stages. And since functions are usually smaller you end up frequently using functions as your readability helpers instead of defaulting to let bindings. Fluent interface - https://java-design-patterns.com/patterns/fluentinterface/#explanation https://java-design-patterns.com/patterns/fluentinterface/#e...
- thom 3y agoI think well-named functions or transducers are possibly better in many cases. I agree that long, complex pipelines of transformations aren’t good to stare at though.
- xmcqdpt2 3y agoThe problem with naming variables is that you then have to deal with their scopes in review. For example, the following Java Foo getSmallFoos() { return foo .stream() .filter(...) .collect(...) } is a lot more readable IMO than if each individual steps were named, especially because reusing stream steps is a runtime failure. Not having names is a good way to enforce linearity in the absence of affine types.
- rtpg 3y ago(This is a rant, i get what you’re saying) I just disagree about me being the issue here. Sometimes I want to make names. They are basically documentation. This is just so normal! Like you can’t just say everything should be written in a point free style. There are times that is more clear and then times where having a name to a value makes subsequent code clearer. Especially given that Clojure has some of the world’s most anemic comment support (no real multiline comment support without futzing with forms? In this economy?) I do agree threading helps by making things more pipeline-y at times though. I just feel like being punished for verbosity is annoying. The verbosity is punishing enough, just let me write it!
- epgui 3y agoThe hardest thing about learning clojure is unlearning other languages… It sounds like you’re just still going through that.
- rtpg 3y agoI’ve programmed in various MLs, I know about the unlearning issue. I have stared into the depths of the monadic transformers, and nothing Clojure does is close to what I've seen on that front. I just think that being able to give names is fine and good. I understand this is more of a challenge because of s-expressions, and I’m not going to entirely fight the idea that sexprs and clojure might be deeply coupled. But one can dream of something nicer!
- roenxi 3y agoI wouldn't say you were the issue. The issue here is Clojure will fight you until you code in the style that it likes. I lost the battle, but Clojure's style is better than my old style so I became a much better programmer. And of course, Clojure is the best language for programming in Clojure's style. If you don't like threading macros, try a 1-let-per-function style. I've seen some very capable Clojure devs who are in the habit of making lots of small, neat functions. It can be a lot prettier than threading macros. It works in Clojure because making small mutations to large objects is cheap - that enables small functions that tweak data structures in a way that would be unsafe in other languages.
- da39a3ee 3y agoIt’s essential to use names in programming to modularize code and introduce different levels of abstraction. Smart people can drift towards a point-free style because they enjoy the feeling that they are capable of comprehending their code. It’s important to resist the introduction of such code and to help such people see that neither they in 6 months, nor their colleagues now, will be able to understand it.