Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Last time I spent a couple klines trying to write some clojure (+ clojurescript), the big thing was that most of the tooling had some really nasty error states. Python has some of this (ctrl+C out of most Python programs and get that stack trace with a bunch of inscrutible stuff), but given I was just starting out it was tough for me to move forwards.

I do think the language + ecosystem has so many innovative and interesting ideas built in. Definitely worth at least reading the history of Clojure paper and internalizing most of the lessons.

This is going to be anathema to everyone but I think there's a nice little ecosystem space for a scripting language that is as serious about performance and code ergonomics as Clojure... but without the parens[0] (and I could do without the nil-punning, but at least there's a bit of a foundation there)

[0]: yes I know that (f x y) is nicer than f(x, y). My problem is more when it comes to things like let forms introducing indentation (I shouldn't have to pay an indent + paren cost when trying to give a value a name!) and other things that just feel like line noise when coming from a language like Python, that just uses the indentation I'm going to put in my code anyways as information



> 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).


(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!


I 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.


The hardest thing about learning clojure is unlearning other languages… It sounds like you’re just still going through that.


I’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!


Well-named intermediary variables often help tremendously with readability though. A language discouraging their use seems like the wrong trade-off decision to me.


The 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.


I 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.


that'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.


If 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.


You 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.


If 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.


the 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


Yes 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/#e...


It’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.


There's definitely some things you learn from mistakes when using a REPL workflow in Clojure or Python, where you debug for a bit and then realize that you just didn't reload or reinitialize something. I feel most people end up adding it to the set of things to sanity check when hitting "can't happen" situations, like "let's add a debug print here to make sure my code change is actually invoked". But that's just a backstop of course, you mainly use other practices in your REPL workflow to keep from hitting it.


I was thinking more about tools like `lein` just barfing up garbage when I did something wrong. This stuff might be totally better nowadays though! It's been many years since I touched the language.


> Last time I spent a couple klines trying to write some clojure (+ clojurescript)

Killing me! My brain says this should be (+ clojure clojurescript)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: