Live data from Hacker News

Six Years of Professional Clojure

engineering.nanit.com

221–230 of 254 posts

Re: Six Years of Professional Clojure

#221

Earlier quoted context omitted.

This line: maybeCache >= head >> initializeCache) is doing exactly what the post is arguing against. getConfDirs is validating the list is non-empty but the [FilePath] list it contains does not encode that information. Now you immediately have to handle the possibility of a missing value from head that you already know cannot happen. This isn't too apparent here since you've combined it into a single expression but i…

I understand what the author is doing. I said this earlier but it bears repeating, the author seems to be more concerned with having a concrete type than simpler code. A reference to `Maybe Cache` is good enough (and preferred). The top-level of your program is precisely where you want to have the flexibility to deal with the above. Furthermore, my example is a much better illustration of the axiom ("Parse don't vali…

The author explains what they mean by parsing in the post:

> Really, a parser is just a function that consumes less-structured input and produces more-structured output. By its very nature, a parser is a partial function—some values in the domain do not correspond to any value in the range—so all parsers must have some notion of failure. Often, the input to a parser is text, but this is by no means a requirement, and parseNonEmpty is a perfectly cromulent parser: it parses lists into non-empty lists, signaling failure by terminating the program with an error message.

So the properties checked by the parser are reflected in the output type. Reifying these properties in the type is what allows the validation to be done once at the top level and avoided throughout the rest of the program. Your complaint about throwing exceptions is focusing on an irrelevant detail in a small example - yes this could have been moved into the main function but doesn't affect the overall behaviour.

However your argument that propagating Maybe values is more idiomatic than parsing into a more precise type is one I - and I assume most - static typing advocates would disagree with. Given the choice you would always prefer an 'a' over a 'Maybe a' since a Maybe represents a point of uncertainty which you would rather not have. As a result, having to chain this imprecision using various combinators is inherently more complex than not having to do so. Yes, using bind etc. is preferable to manually destructing Maybe values but avoiding Maybe is more preferable still.

Re: Six Years of Professional Clojure

#222
post #156

Earlier quoted context omitted.

> type systems are totally unable to express algorithms You don't know much about types if you think that. As for dynamic typing "helping" you to find code that you need to write tests for: There are already far more sophisticated static analysis tools to measure code coverage.

doubler :: Num a => [a] -> [a] doubler xs = take 2 xs Passes the type checker, thanks type system! /s I like static typing, but static typing advocates seriously overstate how much protection the type system gives you. Hickey really said it best: "We used to say 'If it compiles it works' and that's as true now as it was then." As for dynamic typing "helping find code to write tests", that's not a feature, it's a huge…

Proving that your program is consistent is only one of the many benefits that a static type system brings you.

I'd say the main one is that it enables automatic refactorings, which are mathematically impossible to achieve when you don't have type annotations.

Thanks to automatic refactorings, code bases are easier to maintain and evolve, as opposed to dynamically typed languages where developers are often afraid to refactor, and usually end up letting the code rot.

It's also a great way to document your code so that new hires can easily jump on board. It enables great IDE support, and very often, unlocks performance that dynamically typed languages can never match.

Re: Six Years of Professional Clojure

#223

Earlier quoted context omitted.

All these refactorings can only be done automatically and safely if you have type annotations (i.e. core). Without them, all these refactorings can break your code (as in, not even compiling, let alone run).

I believe you're mistaken, but please explain otherwise? None of those seem to require type information from my reasoning (and are also all available in Emacs for Clojure) For example, moving a function from one namespace to another, you know where this function is being used from the require declarations, and you know where you've been told to move it too and where it currently resides. So you can simply change the…

Sure: https://www.beust.com/weblog/2021/06/20/refactoring-a-dynami...

Even Smalltalk's refactoring browser made mistakes which humans had to fix by hand. Which is not surprising, because in the absence of type annotation, the IDE doesn't have enough knowledge to perform safe refactorings.

Re: Six Years of Professional Clojure

#224

> An incoming HTTP request? it is a plain Clojure dictionary. I learned to code in Python. Loved it. Dynamically typed dicts up the wazoo! Then I learned why I prefer actual types. Because then when I read code, I don't have to read the code that populates the dicts to understand what fields exist.

> Then I learned why I prefer actual types

I do like static typing. But honestly, no other PL¹ (statically typed or otherwise) even comes close in terms of the ergonomics and joy of writing software. Nothing is quite enjoyable for me like Clojure. Haskell is great but hard, and I'm years away from claiming I achieved production-ready proficiency with it. I don't want to berate other languages, but I looked into OCaml, F#, Kotlin, Scala, Rust, and a few others. And none of them feel to me as enjoyable as Clojure. After so many years of programming, I finally, truly feel like I love my job. Also, I never liked Python. Maybe just a little, in the beginning. Once I get to know it, I disliked it forever.

-------

¹ I mean languages used in the industry, not counting even more "esoteric" PLs

Re: Six Years of Professional Clojure

#225

Earlier quoted context omitted.

Hey, so my my career path has been C# (many years) -> F# (couple years) -> Clojure (3 months). I understand multithreading primarily through the lens of async/await, and have been having trouble fully grokking the Clojure's multithreading. One of the commandments of async/await is don't block: https://blog.stephencleary.com/2012/07/dont-block-on-async-c... Which is why the async monad tends to infect everything. Cloj…

Multi-threaded code is normally not implemented in an async style, but instead is done where each thread of execution is synchronous. Async style comes into play generally for languages that lack real threads, or as a way to manage callbacks (even if single threaded), or in order to wait for blocking IO without the need for a real thread. So ya, it's idiomatic to use blocking to coordinate between different threads i…

Thanks for the reply - what you say makes a lot of sense. I watched Rich's talk on Async and was like... "cool so `core.async` follows this pattern right?!" ...not quite.

I'll check out your other links though, much appreciated. Also hearing that I should just be okay with blocking is well, good to hear explicitly.

Re: Six Years of Professional Clojure

#226

Earlier quoted context omitted.

I understand what the author is doing. I said this earlier but it bears repeating, the author seems to be more concerned with having a concrete type than simpler code. A reference to `Maybe Cache` is good enough (and preferred). The top-level of your program is precisely where you want to have the flexibility to deal with the above. Furthermore, my example is a much better illustration of the axiom ("Parse don't vali…

The author explains what they mean by parsing in the post: > Really, a parser is just a function that consumes less-structured input and produces more-structured output. By its very nature, a parser is a partial function—some values in the domain do not correspond to any value in the range—so all parsers must have some notion of failure. Often, the input to a parser is text, but this is by no means a requirement, and…

> but avoiding Maybe is more preferable still

You can't avoid `Maybe` in this system. It is in the nature of the problem (as it is designed) that the input might not exist (and therefore a list might be empty). The question isn't one of avoidance, rather, integration. How do we deal with problems like the example?

"Parse don't validate" is a great way to deal with it! Even more convenient is the existence of a tool that can be used to offload all of the redundancy involved when choosing to parse instead of validate (i.e. throw an error).

It is the author's prerogative to value having a concrete value at one specific point in the program (`main`) over demonstrating how using `Maybe` can make parsing a breeze. Clearly you also value (for whatever reason) knowing that a variable contains a value at some specific, rather arbitrary point in the example program[0]. But it is an unfortunate choice given the title of the post.

Not only does the example code in the post not illustrate "parse don't validate" very well, it convolutes the solution considerably. My example above is able to achieve identical behavior in an easier-to-digest flow while also illustrating how parsing instead of validating can be done.

[0] Of course we know that any function to which we `map` to our `maybeCache` will for sure be invoked with an instance of `Cache`.

Re: Six Years of Professional Clojure

#227
post #48

> Pure functions make code design easier: In fact, there’s very little design to be done when your codebase consists mostly of pure functions. Ummm... I am a little bit fearful about your codebase. If you don't see the need for designing your FP system it probably mostly means it is being designed ad hoc rather than explicitly. If you are trying to compare to OOP system done right, you will notice that this includes…

> If you don't see the need for designing your FP system it probably mostly means it is being designed ad hoc rather than explicitly. Why does FP seem to imply that things are designed ad hoc rather than with purpose? I've been working exclusively with FP codebases for the last 5 year, and all designs have been by identifying the domain model and implement it with purpose, with a plan. > includes a lot of work in ide…

I think you misred my comment and think I think exactly the opposite from what I wrote.

To reiterate my point: whether OOP or FP you still need to invest time researching, understanding and writing down domain model and designing your application.

And if you don't, the problem doesn't go away and instead hides in some form of technical debt.

Re: Six Years of Professional Clojure

#228
post #156

Earlier quoted context omitted.

> type systems are totally unable to express algorithms You don't know much about types if you think that. As for dynamic typing "helping" you to find code that you need to write tests for: There are already far more sophisticated static analysis tools to measure code coverage.

doubler :: Num a => [a] -> [a] doubler xs = take 2 xs Passes the type checker, thanks type system! /s I like static typing, but static typing advocates seriously overstate how much protection the type system gives you. Hickey really said it best: "We used to say 'If it compiles it works' and that's as true now as it was then." As for dynamic typing "helping find code to write tests", that's not a feature, it's a huge…

You can very easily write a "safe" version of that function that will not type check and in this case you don't even need dependent types. So: bad example on your part.

Re: Six Years of Professional Clojure

#229
post #228

Earlier quoted context omitted.

doubler :: Num a => [a] -> [a] doubler xs = take 2 xs Passes the type checker, thanks type system! /s I like static typing, but static typing advocates seriously overstate how much protection the type system gives you. Hickey really said it best: "We used to say 'If it compiles it works' and that's as true now as it was then." As for dynamic typing "helping find code to write tests", that's not a feature, it's a huge…

You can very easily write a "safe" version of that function that will not type check and in this case you don't even need dependent types. So: bad example on your part.

So you're saying without dependent types you can express in a type a function that will return double each element in the list thus removing the need to test the function?

If you can do that, that's awesome, but I'm not seeing how.

Re: Six Years of Professional Clojure

#230

Earlier quoted context omitted.

The author explains what they mean by parsing in the post: > Really, a parser is just a function that consumes less-structured input and produces more-structured output. By its very nature, a parser is a partial function—some values in the domain do not correspond to any value in the range—so all parsers must have some notion of failure. Often, the input to a parser is text, but this is by no means a requirement, and…

> but avoiding Maybe is more preferable still You can't avoid `Maybe` in this system. It is in the nature of the problem (as it is designed) that the input might not exist (and therefore a list might be empty). The question isn't one of avoidance, rather, integration. How do we deal with problems like the example? "Parse don't validate" is a great way to deal with it! Even more convenient is the existence of a tool t…

Your example does not achieve idential behaviour at all since it 'parses' an [a] to another [a] and therefore throws away the very property you've just checked. The (NonEmpty a) property encodes the non-emptiness of the list in the type which is then known at every point the list is accessed throughout the entire rest of the program. The point is not just to check the non-emptiness in main as you appear to be implying. Any use of head on an [a] must continually deal with a (Maybe a) even though this possibility has been ruled out. In contrast NonEmpty.head returns an element directly so removes entires chains of Maybes that would be propagated, conveniently with map/bind or otherwise. Parsing allows to replace N + 1 instances of Maybe with just 1 so you can't claim your approaches are the same just because it hasn't been eliminated entirely.
Post reply on HN