3 ms·
I agree. I have limited experience with FPs, and I can get a hang of what is happening in OCaml examples relatively easily, but Haskell examples feel more like
by bbminner 2y ago
I agree. I have limited experience with FPs, and I can get a hang of what is happening in OCaml examples relatively easily, but Haskell examples feel more like something akin to Forth - if I really needed, I could parse it with my brain, but cryptic one liners like that feel more like "puzzles" than an actual useful program.
- kqr 2y agoI feel like there's nuance here that a lot of people don't mention. There are the actual puzzles that have been minified beyond recognition, and one has to work backwards to figure out what clearer code the author started from. I don't think anyone likes this code, but it gets written by people who are more used to writing code than reading it. There is also the incredibly clear, straight-forward code that could have come from any other language. If you manage a sane production Haskell codebase, you'll have a lot of this. In between those two, there is clear code but which you wouldn't find in any other language. This is code that uses well-known Haskell idioms that anyone who works with a lot of Haskell code will recognise, but which look like puzzles to someone who has not worked with a lot of Haskell. These are things like guard (age person >= 18) $> beer to return Just beer to someone of age, but Nothing to someone underage; Uuid.fromWords <$> getRandom <*> getRandom <*> getRandom <*> getRandom to return a randomly generated UUID; maybe (compute_fresh seed) pure cached_value to use a cached value if it exists, computing it from scratch if it did not; or traverse_ $ \elem -> do error "stub" to create a function that loops through a collection and executes a side effect for each element. These look like nonsense to people not familiar with the idioms, but they appear frequently enough that they are no longer puzzles.
- serbuvlad 2y agoMaybe this is a bit off-topic but I have to say: I don't _get_ FP languages. I get the idea of having functions with no side-effects. I think that's a great thing. But this can be achieved in every language. Sure, the statements in the function will have side-effects on local variables, but as long as the function isn't too long, and you can comprehend all of it at a time, that's not a problem. Your examples of "clean code" aren't any cleaner than what can be found in any imperative language. Using C++ (with std::optional), for reasons of familiarity. (person.getAge() >= 18) ? "beer" : {}; UUID::fromWords(getRandom(), getRandom(), getRandom(), getRandom()); cache.has_value() ? cache : (cache = computeFresh(seed)); for (auto& elem : collection) { modify(elem); }
- Tainnor 2y agoOther languages can't help you track that a function that transitively calls 100 other functions is side-effect free, Haskell can. > UUID::fromWords(getRandom(), getRandom(), getRandom(), getRandom()) This ignores that getRandom() is a side-effecting function (it has to be because it returns something different every time). And that's not just a "local variable". UUID.fromWords val1 val2 val3 val4 works in Haskell too, the "extra syntax" is to be able to all that in IO (i.e. with side effects). If you want it in a form that is more recognisable, you could write it as getUuid :: IO UUID getUuid = do val1 <- getRandom val2 <- getRandom val3 <- getRandom val4 <- getRandom return UUID.fromWords val1 val2 val3 val4 but that's unnecessarily verbose if you know the idiom. Your cache example is similarly side effecting.