Live data from Hacker News

Tour of our 250k line Clojure codebase

tech.redplanetlabs.com

161–170 of 237 posts

Re: Tour of our 250k line Clojure codebase

#161

Earlier quoted context omitted.

If a field is an int and the incoming data has a string the parsing shouldn't succeed - instead it should give an error like "expected an int but saw a string". Maybe include the string it saw in the error. Add in the JSON path for bonus debugging points. You shouldn't get to the point of having a CreateTaskRequest with a 0 that shouldn't be there.

I understand and have done that before with XyzRequest classes. What I'm saying is the field shouldn't be an int. It's not the proper level of abstraction for what it represents, and any of: tossing out, transforming, or hiding under layers of abstraction the data that comes into your system like this is a bad idea and is the sort of thing that makes people hate OOP. Especially when perfectly fine alternatives exist…

Interesting. I'd say the proper level of abstraction for the incoming JSON is some structure that can represent all valid JSON. Then you convert from that JSON thing into the form you expect - in this case a CreateTaskRequest (with int fields and whatnot) - which would be the proper level of abstraction for the code that deals with creating tasks.

Is that substantially different from what you suggest?

Re: Tour of our 250k line Clojure codebase

#162

Earlier quoted context omitted.

Your link might be pointing to the wrong example. I assume you wanted to show an example of an atom.

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 the imperative style, because in imperative you can do:

    int a = 10;
    a = ++a + ++a;
Where `a` is now equal to 23, but in Clojure:

    (let [a 10
          a (+ (inc a) (inc a))]
      a)
You now have `a` equal to 22 instead.

This is the difference between the variable substitution evaluation semantic of lambda calculus and the imperative semantics.

Re: Tour of our 250k line Clojure codebase

#163
post #15

If I want to learn Clojure, where is the best place to start? I have a lot of experience with Python/Javascript now, and spent many years in C/C++/Objective C and Java. Also have some Go.

If you are into books, I will recommend Clojure for the Brave and True which is free to read online [1], and Living Clojure [2], in that order. If you're into interactive kata-style problems, there's 4Clojure [3]. Also join the Clojurians's Slack [4] for the community. [1] https://www.braveclojure.com/clojure-for-the-brave-and-true/ [2] https://www.oreilly.com/library/view/living-clojure/97814919... [3] https://www.4…

And Clojurian's zulip: https://clojurians.zulipchat.com

Re: Tour of our 250k line Clojure codebase

#164

Earlier quoted context omitted.

At the end of the day, when we're working with external data, no language is a silver bullet. But you can get pretty far by doing https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va... and yelling at your colleagues when they don't. FWIW, this sort of thing can be done with a dynamic language, too. It's true that some static languages happen to be really far ahead of the curve with this sort of thing. But it…

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-or-nothing choice, and that people were less religious about it.

Re: Tour of our 250k line Clojure codebase

#165
post #159

Earlier quoted context omitted.

100x increase in productivity is a silly, hyperbolic claim no matter who makes it. I'd even be skeptical of a 2x claim, because in 25 years I have yet to see any of these productivity plays actually pan out. What I have seen are small incremental improvements here and there, but you can't point to anything in the recent past that has improved productivity 10x or 100x (unless your old process was just total crap). At…

you concluded something is impossible based on the fact that you have never seen it before? talking about silly claims :)

Yes, that's how observation and personal opinion works.

Re: Tour of our 250k line Clojure codebase

#166

Earlier quoted context omitted.

I like this design for making requests, but I don't like it for serving requests. Because if you don't just make every field a String in CreateTaskRequest, and you want to examine it in some way, then you're losing information. E.g. if you make a field an int, and they provide a non-int value, your CreateTaskRequest can't hold that value so its just gonna have a 0. A class full of strings feels like a code smell - bu…

If a field is an int and the incoming data has a string the parsing shouldn't succeed - instead it should give an error like "expected an int but saw a string". Maybe include the string it saw in the error. Add in the JSON path for bonus debugging points. You shouldn't get to the point of having a CreateTaskRequest with a 0 that shouldn't be there.

My concern with all-or-nothing validation of the sort you're suggesting is that sometimes the invalid data is data that isn't your concern and doesn't matter to the task you're trying to accomplish. It doesn't even have to be a mistake on the sender's side; maybe something evolved, maybe you made the mistake. I prefer defining the minimum I need for a specific function, instead of defining exactly what I expect. For much the same reasons as we scribble in the margins sometimes.

Re: Tour of our 250k line Clojure codebase

#167

Earlier quoted context omitted.

Clojure doesn't solve the expression problem and the expression problem tends to be trivial in dynamically typed languages. Strictly speaking the expression problem is only defined for _static_ type systems because its concerned with _static_ type safety and extensions without recompilation.

It does solve it and doing so was a key design goal of protocols. It's right there in the docs: There are several motivations for protocols: Avoid the 'expression problem' by allowing independent extension of the set of types, protocols, and implementations of protocols on types, by different parties I agree that it's a concern in static type systems, but the same issue rears its head when defining methods which oper…

[deleted]

Re: Tour of our 250k line Clojure codebase

#168

Earlier quoted context omitted.

If a field is an int and the incoming data has a string the parsing shouldn't succeed - instead it should give an error like "expected an int but saw a string". Maybe include the string it saw in the error. Add in the JSON path for bonus debugging points. You shouldn't get to the point of having a CreateTaskRequest with a 0 that shouldn't be there.

My concern with all-or-nothing validation of the sort you're suggesting is that sometimes the invalid data is data that isn't your concern and doesn't matter to the task you're trying to accomplish. It doesn't even have to be a mistake on the sender's side; maybe something evolved, maybe you made the mistake. I prefer defining the minimum I need for a specific function, instead of defining exactly what I expect. For…

When encoding types for requests like this, I'm exactly as strict as I need to be: no more, no less. Any assumption about the shape of the data that my code will make is encoded in the parser, but the parser is very generous with everything else.

For example, if a request has a field I didn't expect, that's not an error, I simply ignore that field because I obviously didn't need it. Likewise if requests start using a more specific schema, such as now sending only positive integers. If, however, the schema changes such that a field I rely on is dropped or changes type, my code will eventually fail anyway as soon as it makes an assumption about the data. Why not fail at the point of entry instead of saving it for later?

Re: Tour of our 250k line Clojure codebase

#169

> And doing things dynamically means we can enforce stronger constraints than possible with static type systems. Can someone please explain this to a novice like me?

Taken literally the claim isn’t true - a Turing complete type system can enforce any constraint that a Turing complete programming language can. But your everyday type systems typically can’t express concepts like “a list of at least 3 elements” or “a number which is a power of 2”.

I think he's referring to the fact that you have more information at runtime (the actual request) than you do at compile time.

But your distinction about the theoretical limitations vs the practical capabilities of current type systems is also a good point.

Re: Tour of our 250k line Clojure codebase

#170

Earlier quoted context omitted.

Your link might be pointing to the wrong example. I assume you wanted to show an example of an atom.

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 imperative. On the other hand, atoms are imperative, as they are mutable and therefore side-effectful.
Post reply on HN