Good Morning!
Morning
morning
đź‘‹
Morning
mogge
I feel like I've done a day's work already - someone made the mistake of not approving my (mostly refactoring) PR until after I'd finished on Friday, so I've had a long time to think about some more refactoring to do
Today I had an interesting chat about “embracing data” in Clojure and in Haskell. Embracing data in Clj is mainly on using map, set, list & vector and building reusable functions over these while Haskell-ish approach is more about describing the domain by types. Funny thing is that my first Clj app was exactly using this Haskell-ish approach (I was LiveScript developer before - LS is Haskell-inspired CoffeeScript) and now it seems to me that often people who use clojure.spec a lot tend to try to get this Haskell-way approach too.
Yeah, I see a lot of beginners pick up Spec and try to "spec everything" as if it were a "type system" and that way lies madness in Clojure...
I am guilty of doing it. Just in prismatic/schema as there was no spec back then.
We went back and forth between core.typed and schema several times and ended up dumping both of them.
We are heavy users of Spec -- but mostly for data validation and (generative) testing rather than function call checking.
It did help me to improve my quality & understand better how data flow through the application. But I don’t think it’s good for reusable libraries and very abstract parts of the app. What were the main reasons to dump core.typed?
Two main things: the type inference was very poor (it's gotten a bit better) so a lot of annotations were needed -- and you could just annotate functions one at a time: you had to annotate an entire namespace or just deal with a wall of warnings; and quite a lot of idiomatic code, especially anything relying heavily on nil-punning, tended to defeat the type checker and we ended up having to refactor code into less idiomatic structures in order to persuade core.typed to type check it.
The Haskell folks always talk about how what makes their type system so great is that nearly all of it can be inferred and you really only need to describe your structured data types, you don't need to annotate functions that operate on them (and they're right about that to quite a degree). So core.typed's inability to ignore or infer non-annotated code kind of made the worst of both worlds: it was extremely fussy (like Haskell's type system) and also extremely verbose (unlike Haskell's type system).
Schema was "better" in that we could just annotate parts of the program but "worse" in that the type checking was only as good as our example-based tests back then -- so it didn't really catch anything that our tests didn't already catch.
I expect if we'd used Schema primarily for explicit validation of data structures -- like we do with Spec today -- we'd have liked it a lot more. But the real leverage we get from Spec in testing is more about generation: either generation of complex, random data for example-based tests or full-on generative testing.
https://corfield.org/blog/2019/09/13/using-spec/ talks about the various ways we use Spec these days.