5 ms·
This, so much. After some 3y spend doing Clojure, having a Haskell codebase is a totally new level of stress reduction. All of a sudden you can stop worrying a
by ff_ 8y ago
This, so much. After some 3y spend doing Clojure, having a Haskell codebase is a totally new level of stress reduction.
All of a sudden you can stop worrying about "what am I getting in this function" and actually concentrate on the business logic.
(No, tests can't cover everything. Yes, we still have to write tests, but now they are all useful. Yes, the thing that types hinder you from doing changes is a lie, it's quite the opposite, much easier to change things since the compiler covers your ass)
- yawn 8y ago> All of a sudden you can stop worrying about "what am I getting in this function" I think what you're saying is something like "this function guarantees that the thing I get here conforms to this shape" and I do like that. Something I don't see a lot of people express is just the simple "I know what the shape of this thing is by looking at the signature". Whenever I find myself reading other people's Clojure code, it feels like I end up spending more time figuring out what is being passed into a function. I have to look around to find the origin of a value. I wanted to tear my hair out the first time I tried reading Ring's source code to figure out how it worked. With F#'s Suave, I can tell what each function gets and what can be done to it--it's self documenting. In Clojure, as the original author you can play around in the repl full steam ahead because the types are in your head at the moment. Those who come after you have to assemble the puzzle with less information.
- gered 8y agoI think you've really summed it up well here. I have exactly the same concerns when looking at large Clojure codebases. It gets incredibly tiresome having to do this and even at the end of it, I'm still left with that nagging doubt "did I miss some detail somewhere?". Hopefully there's a unit test or three to help put my mind at ease but, at least at my work, that's really quite the pipe dream.
- StreamBright 8y agoI usually put these into the description so the next engineer has a better time tracking what is what.
- zmmmmm 8y agoCouldn't agree more, but what I don't understand is that Python is plagued by this problem and nobody seems to have any issue with it. Trying to understand large Python codebases drives me crazy.
- camgunz 8y agoI think a lot of Python engineers don't yet realize they have this problem. It's a classic issue in predominantly OO languages because you often get so caught up in defining objects you lose sight of control flow and data flow. Toss multiple inheritance and metaprogramming on top and it's basically hopeless.
- StreamBright 8y agoIt is funny, I usually know what I am getting in this function in my Clojure code. OCaml (or Haskell) gives entirely different comfort to me, it tells me what makes sense as input and output type for my function. The downside is that I need to define different functions for different types. utop # let avg a b = (a +. b) /. 2.0;; val avg : float -> float -> float = <fun> utop # avg 2 4;; Error: This expression has type int but an expression was expected of type float utop # avg 2.0 4.0;; - : float = 3. user=> (defn avg [a b] (/ (+ a b) 2)) #'user/avg user=> (avg 2 4) 3 user=> (avg 2.0 4.0) 3.0 I think it is a matter of taste which one you prefer. With property based testing and specs you can go very far in term of pre-runtime checks and there was some data about the number of bugs per line published by Github where it turned out that Clojure did not do too bad in comparison to statically typed languages.
- deleted 8y ago[deleted]
- ff_ 8y agoI don't know about OCaml, but Haskell has typeclasses (which look like "polymorphic interfaces"?) so you can define functions generically and put a typeclass constraint to make them work on a certain class of types. E.g. your avg function would be of type: avg :: Num a => a -> a -> a Where your "a" is a number (that is, a type that implements the Num typeclass) This would work out of the box on floats, ints, etc
- StreamBright 8y agoThe signature is not really helpful. Can you show me how do you implement the actual function? If you have operators that are typed like + and +. how can you make this work with Num?
- ff_ 8y agoAlso the + operator is typed with Num; from a Haskell repl: λ> :t (+) (+) :: Num a => a -> a -> a In case of average we need /: λ> :t (/) (/) :: Fractional a => a -> a -> a So our type is going to be constrained by Fractional instead of Num). So an implementation could be (from here [1]): import Data.List average :: (Real a, Fractional b) => [a] -> b average xs = realToFrac (sum xs) / genericLength xs [1]: https://stackoverflow.com/questions/2376981 https://stackoverflow.com/questions/2376981