Live data from Hacker News

Tour of our 250k line Clojure codebase

tech.redplanetlabs.com

211–220 of 237 posts

Re: Tour of our 250k line Clojure codebase

#211
post #185
post #144

While I've used all of them, some of their listed libraries are a little dated IMO. Of course this true of almost all mature codebases. For the sake of the less experienced, I'd point them to these substitutions in particular: Compojure: Reitit (which they used in the front end too, so migration maybe in progress) would be my preference for backend routing. Component: Integrant takes Component's ideas, but prefers th…

In a lage codebase, the cost of switching is huge, and if older libraries work well, there is often no clear incentive to do so. Clojure in general being very stable and backwards-compatible makes it even more easier to just continue using older libraries. So what if the library is "dated"? If it works well, why not continue using it? (I speak from my own experience)

Totally agree, I didn't say they shouldn't continue using them.

Re: Tour of our 250k line Clojure codebase

#212

Earlier quoted context omitted.

Lexical shadowing isn't an imperative thing. Imagine the form: (let [a 10 a (+ a a) a (+ a a a)] a) as being like so: (let [a 10] (let [a (+ a a)] (let [a (+ a a a)] a))) Which you can now re-imagine as the functional composition: (+ (+ 10 10) (+ 10 10) (+ 10 10)) So it has evaluation semantics which allow you to reduce it as described in lambda calculus using α-conversion and β-reduction. And this is different to th…

Nice strawman. Now, how would you restructure the let bindings (without the println) at https://clojuredocs.org/clojure.core/let#example-542692c7c02... equivalently? Wouldn't the ordering of the function executions matter? Could you re-order the bindings arbitrarily and get the same result? I don't think you can. That makes it imperative-as-opposed-to-declarative, even though it may not be imperative-as-a-synonym-for…

You do the same thing:

    (let [a (take 5 (range))]
      (let [{:keys [b c d] :or {d 10 b 20 c 30}} {:c 50 :d 100}]
        (let [[e f g & h] ["a" "b" "c" "d" "e"]]
          (let [_ (println "I was here!")]
            (let [foo 12]
              (let [bar (+ foo 100)]
                [a b c d e f g h foo bar]))))))
And now you can simply evaluate it using variable substitution again.

The difference is that when you say `(let [a 10])` the variable `a` is a constant now, you can effectively replace all occurrence of it within the scope by `10`, and you can do this at compile time.

That's why people say the symbol `a` is bound to the value 10, and not the variable `a` points to the value 10.

Effectively you cannot change `a` within the scope anymore, all use of `a` in that scope will be equal, and so within that scope you can change their ordering freely.

What I think is confusing you here is that `let` gives you syntax sugar so all the scopes are flattened, but each new binding pair is actually inside a nested scope, and so `a` is still immutable. In this case, it matters almost never, but as I showed, it still can, because in an imperative language you could still try to mutate the variable even in the same expression, and that is impossible to do in Clojure.

Re: Tour of our 250k line Clojure codebase

#213
post #60

we have a clojure codebase that's about 100k lines. Honestly I'm kinda fed up with it. Certain 3rd party libs we've used have been abandoned. We wrote our own libs for a major framework and it is failing behind. Too many "I'm very clever" functions that are hard to understand and also have subtle bugs.

It sounds like Clojure just isn't your cup of tea. Both of those problems exist in every other language I've ever used.

Re: Tour of our 250k line Clojure codebase

#214
post #188

I develop and maintain a 60k line Clojure+ClojureScript codebase by myself, so I can definitely confirm that Clojure does allow for smaller teams to maintain larger codebases :-) I also fully agree with this: > Detecting an error when creating a record is much better than when using it later on, as during creation you have the context needed to debug the problem. I made it a rule to perform integrity checks as early…

> I noticed that these days roughly 30-40% of the code I write deals with integrity checks, anomalies and anomaly handling.

Love to hear more about this.

My approach has been to convert errors into data and have behavior that deals with conveying these errors in different ways, but it's always felt too complex for the task. I've just got a lot of stuff dealing with handling errors in the different environments (jvm / browser / nodejs / rn) and across async and non-async code.

Re: Tour of our 250k line Clojure codebase

#215
Why hasn't Clojure provide any java.util.function interfaces integration? for language a that's suppose to "embrace the host" to have "ergonomic" access to the host libs because it itself lacks an ecosystem this seems weird.

Re: Tour of our 250k line Clojure codebase

#216
post #215

Why hasn't Clojure provide any java.util.function interfaces integration? for language a that's suppose to "embrace the host" to have "ergonomic" access to the host libs because it itself lacks an ecosystem this seems weird.

Clojure precedes Java 8.

Re: Tour of our 250k line Clojure codebase

#217
post #215

Why hasn't Clojure provide any java.util.function interfaces integration? for language a that's suppose to "embrace the host" to have "ergonomic" access to the host libs because it itself lacks an ecosystem this seems weird.

Clojure precedes Java 8.

GP means why it hasn't kept up with fundamentals of the host language, Java 8 was released in 2014.

Re: Tour of our 250k line Clojure codebase

#218

Earlier quoted context omitted.

Lexical shadowing isn't an imperative thing. Imagine the form: (let [a 10 a (+ a a) a (+ a a a)] a) as being like so: (let [a 10] (let [a (+ a a)] (let [a (+ a a a)] a))) Which you can now re-imagine as the functional composition: (+ (+ 10 10) (+ 10 10) (+ 10 10)) So it has evaluation semantics which allow you to reduce it as described in lambda calculus using α-conversion and β-reduction. And this is different to th…

Also, I don't think your (incorrect / incomplete) transliteration of the C code example to Clojure demonstrates anything we don't already know; namely that `(inc a)` in Clojure has different semantics from `++a` in C. Of course the Clojure expression you wrote has a different semantic interpretation from the C expression you wrote; they're different expressions. The fact that `++a` is side-effecting doesn't make Cloj…

The difference is that it is not possible to modify the value of `a` in the scope that it is bound too. Try as you want, but you won't be able to write an `inc` that modifies the value of `a` within that scope.

The declarative nature is that you're declaring that you want `a` to mean 10 within some scope. That's why you say "let `a` be 10 in current scope", that's your intention here, for `a` to be 10.

After you've declared that, it holds true no matter what.

Where as in the imperative style you say: put 10 at place `a`. This is variable assignment, there's a place which is refered too as `a` and inside that place you can set values and change them at will.

At any point you can instruct the language to change what value is at place `a`, there are no restrictions to the instructions you can give.

It's a bit of a oversimplification to claim that imperative programming is distinguished from declarative programming by assuming a lack of ordering in the latter.

Functional programming is not prevented from expressing order, rather it is less able to express random accidental order at the operational semantics level.

At the end of the day, it's very much about the computational model, imperative is based on Turing model, and Functional on the lambda calculus. Because the latter doesn't depend on a global mutable running state, it is said to be declarative, in the sense that what you see is what you get, you don't need to keep track of what the memory currently has to proceed to the next step.

That said, you're right that Clojure supports some forms of imperative programming as well, but let isn't one, your parent commenter pointing to Atom was more on point. That's what you'd need to do to get back a more imperative `let`:

    (let [a (atom 10)
          a (+ (swap! a inc) (swap! a inc))]
     a) 
 
Will now give you 23.

Re: Tour of our 250k line Clojure codebase

#219
post #215

Why hasn't Clojure provide any java.util.function interfaces integration? for language a that's suppose to "embrace the host" to have "ergonomic" access to the host libs because it itself lacks an ecosystem this seems weird.

This is actually one of my rare criticisms of Clojure. There's a patch, but there was apparently concern about perf. Personally, if I had to do a lot of interop like this, I'd pull it into a custom build, as it wasn't large. I've not kept up with it lately, though, as I haven't needed to do a lot with Kafka recently.

Re: Tour of our 250k line Clojure codebase

#220
post #95

Earlier quoted context omitted.

Seconded. I work in a clojure codebase that were trying to get out of. There's just dead libraries everywhere and stuff that maintained by one person that gets no updates at all. That or we just end up making functional "wrappers" around Java libraries and at that point we might as well just write straight Java. Also yea everyone wants to be so damn smart having macros within macros within macros that no one knows wh…

I've been using Clojure for almost 10 years and writing macros has always been discouraged in the community. You don't see too many of them in the wild, and for good reasons. If you're writing macros on a daily basis, you better have a really good reason for it.

The codebase im in is 10+ years old and macros are everywhere. Were macros a super hyped at one point in the past? I'm fairly new to Clojure just by virtue of being part of this system rewrite but I see stuff everywhere in the code im working in like in the article where there just seems to be macros that accomplish nothing except obscuring already built in constructs for the sake of maybe saving a few lines of code or combining one or two statements into one macro.

Its maddening to just be constantly mentally unpacking this stuff and searching through docs only to find out "Oh its not a clojure thing, someone wrote a macro for this"

Post reply on HN