I am so glad the author has been able to pull this off, and it is one of those "why didnt anybody think of this before, it is so obvious!" moments.
I am so glad the author has been able to pull this off, and it is one of those "why didnt anybody think of this before, it is so obvious!" moments.
To clarify:
1. You can’t have more than one function with the same name in the same module.
2. Standard record accessors are functions.
(emphasis added.) I'm still a Haskell beginner, so I'm not the best person to comment on how this works out in practice.
Having said that, a lot of projects seem to have a general Types.hs where they declare all the types they share between modules. This is a great solution for that.
Personally I only use records for large-ish "configuration" datatypes which and I just don't use that many of those for it to be a real problem.
Between all of these, it's much less painful than you might expect, though there certainly remains some pain.
Frankly, I have run into only one circumstance where I wanted to throw my computer through a wall due to record inefficiencies... and it only came up while trying to hand translate an the XML specification for ClinicalTrials.gov. It turns out there was a much simpler way to tackle the problem anyway.
In practice I don't significantly care about the "record problem" in Haskell. It is a pain sometimes, but it is not so bad a pain to trade in complexity everywhere like one would have to to use one of these "batteries included" record replacements. As libraries they're alright.
I'm not sure exactly what its current status is in terms of production-readiness but I believe it is still very active. The GitHub repos seem to be chugging along.
https://ocharles.org.uk/blog/guest-posts/2014-12-23-static-p...
https://www.fpcomplete.com/blog/2015/01/announcing-lts-haske... http://www.stackage.org/
> cabal hell can really suck
Agreed, prepare to be happy:
https://www.fpcomplete.com/blog/2015/01/announcing-lts-haske...
> Haskell takes much longer to compile than Go
Also agree, however -O0 in your .cabal file's ghc-options (when -fdevelopment is true) and using -j${NUM_CPU_CORES} can really help.
I'd say the other biggest warts or deficiencies which are being actively discussed are related to the module system, e.g.: https://ghc.haskell.org/trac/ghc/wiki/Backpack ; I also don't find this to be a huge pain point, but I'm told by people with experience with ML-style modules that we're really missing out with the module system we have.
The record problem is not nearly as much of a problem as this discussion makes it seem. Discussions about this problem are invariably going to make it seem like a bigger issue than it is. It's too early to tell, but if this solution really does solve the problem as well as it says, then it will definitely be nice to have, but I don't actually think about this problem very often in day to day work. And lenses have already adequately addressed a significant portion of those pain points.
EDIT: Oh, another point. The fact that this post exists at all is a testimony to the power and flexibility of Haskell. You don't see these "issues" being discussed in other languages because they (other than lisps) don't give you the power to play around with things this close to the language level. So in most other languages the discussion could never happen. And if it did, it would be someone going off and writing a new language.
Haskell's main libraries are based on a simpler model of computation than conventional languages (Scheme/C/JS/Python/...). In this model of computation only two things happen: (a) you define things, (b) expressions are reduced by the language to simpler forms. Furthermore, Haskell's default tendency is to wait when reducing a structure until it's absolutely necessary to reduce it, meaning that you get some nice features (like every list being a generator which can refer to itself in its definition).
Anything which doesn't have an easy model as an expression-reduction (i.e. any side-effect) is more complicated in Haskell. Writing code which is performance-critical (and thus managing when those reductions actually happen) is a bit complicated too.
If only one complicated thing is at play, things are actually pretty tame. So you need multiple of these happening at once before things look a little dicey.
So, in Python you can easily write this:
state = 0
for x in list:
if x % 2 == 0:
state += x
else:
state = state * (x + 1) - x
print(state)
In Haskell, the I/O side-effect of `print` is complicated and threading stateful updates through this sort of for-loop is likewise complicated.One way is to use a high-level idea called "Monad transformers" to keep the complexity down:
import Control.Monad.State.Strict
flip runStateT 0 $ forM_ list $ do
state <- get
if even x then
put (x + state)
else
put (state * (x + 1) - x)
state <- get
lift $ putStrLn $ show state
A different way (doing it all with a recursive function) looks like this: compute list (return 0)
where compute [] prog = prog
compute (x : xs) prog = compute xs $ do
state <- prog
let new_state = if even x then state + x else state * (x + 1) - x
putStrLn $ show $ new_state
return new_state
This works by having an "accumulating parameter" (prog) which is a computer program; each time we run this recursive function we create another program (do) which reads the last value that prog yielded (state), transforms it into new_state, does the side-effect, and then yields the new_state.Both of these get the point across but neither one is quite as simple as the Python syntax.
On the flip side, we've been speaking about the above Python code as if we knew exactly what it does, but we actually don't, because we don't know the type of x, so we don't know what x.__mod__ does. For that matter, the list could have two different things with different `__mod__` values and it's possible that the type of `x` could taint the type of `state` so that `print(state)` does something completely unexpected.
In Haskell you can prevent all of this by changing `list` into `(list :: [Integer])`.