I think I've solved the Haskell records problem
nikita-volkov.github.io
nikita-volkov.github.io
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.
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.
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.
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.
> 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.
https://www.fpcomplete.com/blog/2015/01/announcing-lts-haske... http://www.stackage.org/
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...
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])`.
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.
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.
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.
Between all of these, it's much less painful than you might expect, though there certainly remains some pain.
(emphasis added.) I'm still a Haskell beginner, so I'm not the best person to comment on how this works out in practice.
https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedReco...
https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedReco...
[0] https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedReco...
tl;dr: The patch worked but was messy/overly complex, so it needs refactoring before it's merged.
Could anyone comment on this:
"Haskell syntax parsing needs to be reimplemented in the “record” library"
That sounds like a big, unending task. How hard would it be for this to be implemented as an actual Haskell feature, without quasi-quoting etc.?
But that said, Haskell's LANGUAGE extension system does allow things like this to be easily added to the language in a smooth way.
However, just adding some syntax does not finish solving the problem: one still needs to figure out how to get everything to typecheck under all use cases (and all type extensions) and also to operate efficiently at runtime. This article does that (although it isn't actually explained that well in the article).
Also a bit disappointed that it didn't make the cut - it seemed like a good pragmatic solution!
That's how OCaml records work, too.
Nice record variants which completely solve this issue at the library level, IMO, exist. But the types are more complex than what most people want to deal with in practice.
map (\x -> x.slot) listOfFoos
Pretty sure that x has type `Foo` in this case.One could then simply disallow the use of `HasField "slot" b a` in the domain of functions (pretty sure it can never appear in the range). This would then require the programmer to provide annotations if a more concrete type can't be inferred.
foo :: (Num a, HasField "slot" a b) => b -> b
foo x = x.slot <~ 1
Technically, depending upon the semantics of records (how anonymous they are) it could even be that the input argument has a different type as the output argument, e.g. foo {} ==> {slot = 1}
Anyway, there's a lot to be said here but I think that this isn't merely a syntactic difference. Even the idea that map (\x -> x.slot) listOfFoos
could be inferred would require mixing type inference and syntax de-sugaring.What I'm arguing is that anonymous records are unnecessary and it's silly to insist that problems with anonymous records should derail a good solution for concrete ones. But you are right that it's a bit more than just syntactic.
Wouldn't update do that? Making up further syntax...
(\x -> prototype.slot = x) :: HasField "slot" b a => b -> a
Or am I missing what you meant by "appear in the range"?Since you should know the type of the first argument to '.', it is possible to disambiguate, but it would be a pretty significant corner case in the grammar.
EDIT: Reading the article a second time enlightened me.
If I understand correctly, when haskell compiler sees [foo|some dsl language inside|] it knows, that it should use parser asigned to foo on string inside of quasi-quotes.
Long time ago, I tried learning this using this tutorial http://quasimal.com/posts/2012-05-25-quasitext-and-quasiquot...
(let ((foo "hello"))
(quote (My name is foo)))
> (My name is foo)
An unquoted value is interpreted completely: (let ((foo "hello"))
(My name is foo))
> Error: "My" is not a function
A quasiquoted value is literal by default (like a quoted value), but we can selectively "unquote" parts, to have them interpreted: (let ((foo "hello"))
(quasiquote (My name is (unquote foo))))
> (My name is "hello")
As you can see, in LISP we can use quasiquotes on pretty much anything. Most other languages with quasiquotes only support it in strings; for example in PHP ' is the quotation mark and " is the quasiquotation mark: $foo = "hello";
echo 'My name is $foo'; // Gives 'My name is $foo'
echo "My name is $foo"; // Gives "My name is hello";
Depending on the language, some require explicit unquoting (eg. ${} syntax), others require explicit escaping (eg. \$ syntax).The article uses two forms of quasiquotation marks: [r| and |], and [l| and |]
For example:
person :: Person
person =
[r| {name = "Yuri Alekseyevich Gagarin",
birthday = {year = 1934, month = 3, day = 9},
country = {name = "Soviet Union", language = "Russian"}} |]
Here the value of "person" is quasiquoted, so some parts of its contents (eg. the "{", "}", "," and "=" syntax, along with the "name, "birthday", "year", "month", "day", "country" and "language" tokens) will take their 'literal' values in the record-building language. The other values, '"Yuri Alekseyevich Gagarin"', "1934", "3", "9", '"Soviet Union"' and '"Russian"' will be interpreted as Haskell values, and the results (various Strings and Nums) will be used in the record-building language, instead of their literal tokens.I don't know how to feel about the use of template Haskell in this lib (beyond the fact that it seems like it works great and avoids the curse of the weird error messages). I'm tempted to see it as a proof that, while deconstructive pattern matching is great (I really mean it!), it's nearly impossible to get rid of antediluvian dot accessor syntax/semantics.
Either way I'm too tired and incompetent with Haskell to have a meaningful opinion on that.
Usable records are just an icing on the cake, but it's the cake that matters.
(Not that there aren't good reasons to use Haskell instead of Scala, but Scala has everything on your list)
So yes, Scala might have an edge over Haskell here and there, but IMO, as of a person who happens to have an extensive experience in Scala, in general it is majorly inferior compared to Haskell.
What I like about Ceylon is that it's pragmatic, it's great for functional, objective and imperative programming. One thing that I disliked about Haskell is that it can be a bit cumbersome to do imperative and side-effect programming (which often the most readable and understandable way to write code).
> can be a bit cumbersome to do imperative and side-effect programming
On the contrary, I think Haskell is great for imperative programming. It allows you to operate at higher levels of abstraction and create more abstract imperative constructs than most other languages. If you want to mutate things, there is a small cost to construct IORefs, STRefs, and the like, but I think that's actually a good thing because it correctly incentivizes the reduction of side effects. And again, much of this boilerplate can be gotten rid of. See https://github.com/ekmett/lens/blob/master/examples/Pong.hs#... for an example of some really nice imperative code made possible by the lens library.
Ceylon on the other hand really clicks with me more than the many other languages I've looked at.
If you define productive as the number of lines of code you are able to write in the next month after you start learning the language, then Haskell will probably not be more productive. But if you define productivity as the amount of time it takes an experienced Haskell developer to build an application with a certain defect level, then I think Haskell is indeed more productive. And I believe this phenomenon becomes more noticeable the larger the project. Purity completely eliminates entire classes of bugs and allows you to be much more confident about the behavior of code when looking at it in isolation. It also substantially improves code reuse for the same reason [1].
Writing new code is one thing, but maintaining existing code is an even bigger area where I think Haskell has higher productivity. Haskell allows me to fearlessly refactor code in a way that no other language I've used comes close to. Purity is a big contributor here too.
There are probably certain classes of problems for which Haskell still needs improvement in the library ecosystem in order to compete. But I'm in this for the long game and am willing to deal with this in order to get to a better capability over the long term. The ecosystem has surprising depth already and is growing quickly even though the community is still relatively small. For instance, a relatively recent improvement is that Haskell now has more complete OpenGL bindings than any other language [2].
So in short yes, there are definitely people who use Haskell because they believe it makes them more productive. I personally believe this is related to its elegance.
Ceylon's subtyping is the big thing that is coming to mind right now that makes me prefer Haskell's type system. The purpose of a type system in my mind is to prevent bad programs from being written. But when you combine subtyping with generics you get a problem. Nothing keeps you from looking for an Employee in a Set<Int> because both Employee and Int are a subtype of Object and the covariant use of Set<Int> can be promoted to Set<Object>. But that's a nonsensical thing to do and should obviously be a type error. This kind of thing comes up a lot in practice and is a place where Haskell's type system is safer.
Oh come on, at least provide a couple examples with a claim like that...
> One thing that I disliked about Haskell is that it can be a bit cumbersome to do imperative and side-effect programming
From a not quite yet ready to release everywhere web scraping library of mine in Haskell:
main = runScraper $ do
get "https://www.paypal.com/login"
postToForm (Name "login_form") (Just creds) AllVisible
get "https://www.paypal.com/myaccount/home"
cursor <- liftM (fromMaybe (error "Couldn't get cursor")) getCurrentCursor
liftIO . putStrLn $ "Your Paypal balance is: " <> getPaypalBalance cursor
where creds = [ ("login_email", "email@example.com") -- put your credentials here
, ("login_password", "password")]
An example of using lenses and imperative programming to create pong[0]: -- Update the paddles
updatePaddles :: Float -> State Pong ()
updatePaddles time = do
p <- get
let paddleMovement = time * paddleSpeed
keyPressed key = p^.keys.contains (SpecialKey key)
-- Update the player's paddle based on keys
when (keyPressed KeyUp) $ paddle1 += paddleMovement
when (keyPressed KeyDown) $ paddle1 -= paddleMovement
-- Calculate the optimal position
let optimal = hitPos (p^.ballPos) (p^.ballSpeed)
acc = accuracy p
target = optimal * acc + (p^.ballPos._y) * (1 - acc)
dist = target - p^.paddle2
-- Move the CPU's paddle towards this optimal position as needed
when (abs dist > paddleHeight/3) $
case compare dist 0 of
GT -> paddle2 += paddleMovement
LT -> paddle2 -= paddleMovement
_ -> return ()
-- Make sure both paddles don't leave the playing area
paddle1 %= clamp (paddleHeight/2)
paddle2 %= clamp (paddleHeight/2)
From "Program imperatively using Haskell lenses"[1]: battle :: StateT Game IO ()
battle = do
-- Charge!
forM_ ["Take that!", "and that!", "and that!"] $ \taunt -> do
lift $ putStrLn taunt
strike
-- The dragon awakes!
fireBreath (Point 0.5 1.5)
replicateM_ 3 $ do
-- The better part of valor
retreat
-- Boss chases them
zoom (boss.position) $ do
x += 10
y += 10
As for "which often the most readable and understandable way to write code"... let's find out! I'll trade some examples for common tasks if you'll do the same :)Now you either give up everything else Haskell gives you, or you've created a new language. Do you see how both of those solutions are problems in and of themselves?
No, I'm presupposing that Haskell brings useful things to the table.
Curiously, Racket lacks most of these pitfalls, except maybe that the macro system is so good you'll end up toying around with it too much. With some discipline it's essentially the perfect engineer Lisp, though.
Of course it happens in other languages. But I'm certain of two things, a strong predicate and a less strong one:
1) A thing X that happens in all/most languages, doesn't happen to the same deegree in all of them. Of this, I'm 100% certain.
2) Playing with/melding the language instead of being really productive tends to happen in Lisp a lot more (this is a personal observation, based on 1).
>A recent article made a pretty good case for Perl being the most productive language ever but I don't see people ditching their current favorite language for Perl.
For that to happen (a) the article would have to be correct (b) easy for people to verify that it is so, (c) people should have the tendecy to migrate to what's more productive (instead of what they like, are used to etc).
So I don't think the fact that "people don't migrate to Perl" despite "an article about Perl being the most productive language being published" means anything with regards to whether Lisp programmers tend to be non-productive in the real world.
I completely disagree. It's much simpler to use a language with a few simple, powerful constructs, which can be used to build everything else as libraries.