Live data from Hacker News

Tour of our 250k line Clojure codebase

tech.redplanetlabs.com

201–210 of 237 posts

Re: Tour of our 250k line Clojure codebase

#201

Earlier quoted context omitted.

Nope. The sequence of statements in the (let) block is imperative. Atoms are actually an example of how the values of references are updated functionally. Clojure has facilities for both imperative and functional style.

"The sequence of statements in the (let) block is imperative." The println in the example is imperative, as it has side-effects, but a let block having an ordering of bindings is not inherently imperative. You can replace any let block with a set of nested functions: (let [foo 12 bar (+ foo 100)] [foo bar]) ((fn [foo] ((fn [bar] [foo bar]) (+ foo 100))) 12) Sequential ordering of 'statements' is not automatically imp…

Hmm. I usually think of "imperative" as "as opposed to declarative", and sequential ordering is one of its hallmarks in that context. And for Clojure atoms specifically I think of "functional" as in Okasaki's "purely functional data structures".

Language is hard.

Re: Tour of our 250k line Clojure codebase

#202

Earlier quoted context omitted.

Nope. The sequence of statements in the (let) block is imperative. Atoms are actually an example of how the values of references are updated functionally. Clojure has facilities for both imperative and functional style.

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-side-effecting.

Re: Tour of our 250k line Clojure codebase

#203

Earlier quoted context omitted.

Nope. The sequence of statements in the (let) block is imperative. Atoms are actually an example of how the values of references are updated functionally. Clojure has facilities for both imperative and functional style.

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 Clojure code that depends on sequencing not imperative (imperative-as-opposed-to-declarative, that is).

Re: Tour of our 250k line Clojure codebase

#204

This is the first time I'm looking closely at Clojure code and it seems wholly antithetical to the practice of software engineering. Not very readable, too many ways to create magical jank, odd coding conventions, and it looks like refactoring and maintenance would be a nightmare. I'm very glad the community has had the collective sense to not adopt this widely.

By "looking closely at" do you mean you've actually grokked the language and used it to build anything?

Re: Tour of our 250k line Clojure codebase

#205

Earlier quoted context omitted.

That's an interesting perspective (and now that you mention it I can think of someone at work who loves types and also tends to break stuff). I myself love static types because it allows me to avoid certain classes of errors and I try my hardest not to introduce bugs. Static typing also greatly informs my workflow. I tend to practice type driven development to the extent possible so when I go back to dynamic language…

Just as bad are those that check in code that that works, but no longer makes logical sense when you read the code. The following is a simple case of what I'm talking about: var flag_is_unset = flag_is_set I generally land more on the dynamic side of things, but there are certainly problem domains where I love types. The more closed and "mathy" the domain, the better I think types fit. I just wish it was less an all-…

I keep wishing someone would take C#'s concept of dynamic references and run with it.

Optional static typing, like what you get in Typescript or MyPy, is interesting, but defaulting to dynamic and making static opt-in undermines a lot of the potential benefit of static typing. I don't think that it works the other way around, though. My hunch, which I am not particularly prepared to defend, is that, at least as long as you've got good type inference, defaulting to static and making dynamic opt-in does let you keep most of the practical benefits of dynamic typing.

I guess it's kind of like unsafe code blocks, also from C#: Pointers and weak typing can be very useful in some circumstances, but I'm generally much happier having the compiler try to guarantee as much as it can, and then sometimes be able to tell it, "Nah, hang on, I got this one."

Re: Tour of our 250k line Clojure codebase

#206
post #189

Earlier quoted context omitted.

> Each part of the system can't know what it needs, Yeah it can. It needs what it needed before. If you have a system that is shipping stuff to customers, it doesn't have to care about the new childhoodPetName Field that you've added for a different part of the system. This is especially relevant if you handle any kind of description of the real world, e.g. in robotics or medicine, and have multiple distributed syste…

If field is childhoodPetName and other case labels are e.g., mothersMaidenName, and the common operation is to perform input sanitization, then this new field would have introduced a security hole if it's just ignored. It's a matter of correctness enforced at language level, and why languages like Rust's match statement enforces all checks at compile time by default.

I agree this is where enum coupling is beneficial. What I'm saying is that, if those fields are later used for other things in your system (like persisting to a DB or running reports or something), you likely don't want to be re-validating the strings as closed enums, that's probably over-coupled.

I don't think there's a silver bullet here, or some rule of thumb that can save us from these schema issues. It's messy and imperfect; we have static type systems that can help us locally but they are not global silver bullets. Unfortunately we still have to use our judgment to find the right balance, because the "perfect" solution requires knowledge of the future.

Re: Tour of our 250k line Clojure codebase

#207
post #95
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.

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…

Yes, we have some horrid macros. I think the intent was to hide infrastructure details from business logic, but it turned into a big mess.

Re: Tour of our 250k line Clojure codebase

#208
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.

Would you mind sharing the abandoned libs/framework? When you say you have too many "I'm clever" functions, do you mean within code your team wrote, or in the ecosystem at large?

it is a couple of the clojurewerkz libs.

the "I'm very clever" defns and macros that I'm referring to were written by our team.

Most of the 3rd party libs are pretty good. Except for them being not maintained and not keeping up with the latest versions of databases, etc.

Re: Tour of our 250k line Clojure codebase

#209
post #152

Earlier quoted context omitted.

When you have higher-kinded types you can build that kind of thing yourself. Dependent types going further in that direction. Take a look at Idris.

I will, thanks. I never had a chance to catch up on the "state of the art" in typing systems. I'm not even sure if my "types as a set of tags" idea makes much sense - perhaps it decays to what is typically understood as types. Or perhaps it hits computability problems. I did some mental experiments on a "set of tags" type system last night, and I quickly realized the complexity will be around deciding when to keep a…

Would it be ok if you could define relations between tags in code integrating them?

    Contradicts sorted foobar
    Independent sorted nonempty
    Independent foobar nonempty
Additionally there should be tag that contradicts everything by default.

Re: Tour of our 250k line Clojure codebase

#210

Earlier quoted context omitted.

Just as bad are those that check in code that that works, but no longer makes logical sense when you read the code. The following is a simple case of what I'm talking about: var flag_is_unset = flag_is_set I generally land more on the dynamic side of things, but there are certainly problem domains where I love types. The more closed and "mathy" the domain, the better I think types fit. I just wish it was less an all-…

I keep wishing someone would take C#'s concept of dynamic references and run with it. Optional static typing, like what you get in Typescript or MyPy, is interesting, but defaulting to dynamic and making static opt-in undermines a lot of the potential benefit of static typing. I don't think that it works the other way around, though. My hunch, which I am not particularly prepared to defend, is that, at least as long…

> defaulting to dynamic and making static opt-in undermines a lot of the potential benefit of static typing. I don't think that it works the other way around

Yes, there's certainly truth to this. However, I write a lot of TypeScript and it provides an _exquisite_ on ramp in that you can add types gradually and telling the compiler not to worry about it can be super useful, especially if you're dealing with poorly behaved third party stubs that may have diverged from the actual implementation. The fact that you can use TypeScript as a super-duper linter _or_ try to maximally leverage its powerful type system is a huge strength. It's also very helpful for adoption. As frustrated as I get sometimes with TypeScript's unsoundness and the lack of pattern matching, the fact that I get to use it instead of JavaScript (and have also got half the company using it!) is a huge win.

Post reply on HN