top | item 18347229

(no title)

akmiller | 7 years ago

To be fair, I wasn't comparing the two. I've never used Haskell (for more than just learning/tutorials).

I would suggest that if you have runtime bugs popping up in Clojure programs then that would suggest the inputs to functions (since they should be primarily pure) are not being validated which can easily be accomplished. I would imagine this needs to be done in Haskell as well since just verifying types does not indicate valid data.

discuss

order

matt-noonan|7 years ago

In Haskell designs, developers tend to be quite careful about setting things up so that verifying types does indicate valid data. Coming from other languages (C++, Common Lisp, and Python in my case), it can be a little surprising just how often and how easily you can make this happen.

akmiller|7 years ago

You can do similar things with spec for Clojure. As I stated in another response that is opt-in obviously so it requires more discipline perhaps but it is definitely available.

lgas|7 years ago

> To be fair, I wasn't comparing the two.

Well, the statement you were disagreeing with from the post you responded to was:

> You can't refactor Clojure without fear like in Haskell.

You go on to suggest you can achieve a similar experience in Clojure by "depending on how you write your code". This simply hasn't been my experience. Just "writing your code the right way" solves almost every problem that arises in programming, but just isn't always feasible in practice (on a team of developers with mixed skill levels, operating under deadlines, etc).

Re. validation, as Matt Noonan mentioned, in Haskell the goal is often to build/leverage correct-by-construction data types which obviate the need for any validation.

A simple example of this would be the `NonEmpty` (list) type.

If you have a function that pulls a list out of some key in a clojure map and it's intended that it always be a non-empty list you still need to check if actually is or not before using it because you have no control over what the caller passes to you. If the caller never sends an empty list you're fine, but if they do and you don't check for it, you've got a bug. Even if you do check for it, there's often nothing sensible you can do at that point since the local function shouldn't know anything about it's calling context, so you have to raise an error or return a nil or something.

On the flip side, in Haskell instead of using a map you would be likely to create a specific data type, and in that data type you would declare the non-empty field to be of the `NonEmpty` type. The first immediate benefit you get is that you no longer have to do any of these checks for the list being empty (or nil, or something else instead of a list) and instead just write your algorithm over the list. Among other things this results in cleaner, simpler, and less code in your immediate function.

But there's another benefit which is now anyone that calls your function has to have constructed a `NonEmpty` list before they call your function. The impact of this essentially naturally propagates the need to construct that `NonEmpty` list to the right place in the code. Maybe it really is just the caller to your your function that needs to take a regular (possibly empty) list that it has and package it up into a `NonEmpty` to call your function... and in that case you still get the benefits of code that shorter, simpler and more clearly communicates it's intention, but where this really shines is when that requirement to pass a non-empty list makes you realize something about the nature of your problem, and you let that `NonEmpty` propagate all the way out towards the boundaries of your application.

Then you end up in a situation where a) all of the code that touches that field anywhere is simpler, clearer, etc. but more importantly b) if someone does send you malformed data with an empty list (say via JSON over a web API or similar) then the code that deserializes the JSON into the `NonEmpty` will fail and you will get an error that says something like "Couldn't decode a YourCustomType from {whatever it was trying to decode}" at the very moment that the bad data tried to enter your system -- instead of just reading the JSON into a map because it was well formed and then letting the record with the empty list in it bounce around until it hits a function that assumes it's non empty at which point you may have little to no information about the provenance of the data or other details that would make easier to solve the problem.

akmiller|7 years ago

Exactly, and I was responding to the fact that I never fear refactoring my Clojure code.

Your examples of the type safety can all be mimicked with spec in Clojure. Granted spec is opt-in (but I'm guessing so is some of the more detailed type safety attributes you are talking about like NonEmpty).

Anyhow, to each their own and one persons experience isn't likely to be the same as the others so I'd encourage everyone to try out many languages. Some languages click with people more than others do so it's always worthwhile to experiment.