For the uninitiated, this solves a major (major!!) pain in the Haskell community: the lack of namespacing. If you declare two records with an 'id' field in the same module, you would get conflicts. People have been either using prefixing (fooId and barId) or splitting up modules to solve this problem, which can both considered to be ugly hacks.
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.
Ha, wrong assumption(sic) on my side. He's been part of the research side of things for so long. To my defense his publications are in the form of research papers.
Could you expand on what's the scope of the 'record' intrinsic? Is it used for as many things as Python's `dict`? I sure hope not, otherwise I'm wondering how anyone could have gotten much done in this language before.
It sort of makes me wonder how much of the hype around Haskell is overblown, this seems like pretty basic stuff. Haskell has all kinds of interesting properties but the way it is presented is usually as though it is an end-run around just about every problem in other programming languages. A dose of up-front honesty about potential issues would be handy. Are there any other things that are problematic in Haskell that other programmers from lesser languages usually take for granted?
I've been writing Haskell professionally full-time for five years now and in my opinion no other language even comes close. We've even rewritten all our Javascript in Haskell because maintaining a significant body of Javascript was becoming too much of a burden. So no, I don't think it's overblown at all.
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.
None of haskell's warts are near bad enough that they should put you off learning the language or using it successfully. Records are a particularly weak point (sort of the simplest thing that could possibly work) which some people care very much about, and others don't care about at all. I think it depends heavily on the sort of code you're writing; when I'm writing libraries I never use records, but others find themselves working with very wide product types with lots of `Int`s or whatever that need to be disambiguated and for them this is a big deal.
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.
I switched from Python and Go to Haskell over the last year. Haskell takes much longer to compile than Go and cabal hell can really suck. Other than that it's been absolutely fantastic.
I think the existence of ghc-mod for incremental compiling and the interpreter repl really don't make compile times a big deal. It's a bigger issue in languages where you actually have to fully compile over and over as a part of your dev loop. This isn't something I ever do in Haskell because of said tools.
For fun I'm attracted to various high-level information processing stuff and I do backend web dev at work. Haskell's definitely a Python replacement for me, I'm not writing any OSes in it.
Not the parent, but I made the same transition a couple of years ago. I emailed you something I've wrote in Haskell that might be relevant to you but didn't want to publicize too much yet.
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.
Not to bombard you with too much at once, but one interesting CloudHaskell-related extension that's landing in GHC 7.10 is Static Pointers: safe references to "static" code (constant values, functions, etc.) that can be passed between running instances of a program.
I think you're being downvoted justly, but I think you still deserve an answer to your question (rather than just an onslaught of "hey, don't judge everything by this one thing" type posts).
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])`.
It's essentially Haskell's version of a class, except Haskell isn't object-oriented. It holds data. So yes, you could view it as a dict, of sorts. Except each key in this dict/record generates a top-level function.
How did having no namespaces in these things not become a massive headache for everyone? That would be the absolute killer argument for me to immediately switch to a different language if somehow possible.
The main reason it's not a massive headache is simple: Records aren't used THAT widely. Sure, in C++ or Java everything has named fields, because that's the only thing you have. Most Haskell datatypes use ADTs without record syntax, which means fields aren't named.
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.
First, there are namespaces, it's just that they are associated with files instead of particular data types. Secondly, the naming issue only tends to become an issue when you have many types which all have similar internal structure. It turns out that, in Haskell at least, you can avoid needing this often by simply factoring out the shared structure (!). If this is not reasonable then there are boilerplate-y ways to use typeclasses for proper overloading (which also respects namespaces better than this solution does).
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.
So far it's more an inconvience -- in practice it doesn't surface that often, since modules represent the namespaces in Haskell. If you run into naming conflicts very often, chances are your modules are not isolated enough. You don't use records as often as you use classes in an OOP language, for example.
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.
It's not quite true that you have no namespaces. Rather, in most languages a class or struct creates a namespace. In Haskell, it does not. Record accessors are still constrained by module. There are also data types where the fields are not named. And typeclasses can define names that can be used across multiple types.
Between all of these, it's much less painful than you might expect, though there certainly remains some pain.
My thoughts are that it is something of a headache, but the benefits of the language outweigh the pain-points, like this one, in many cases. I'd guess it's seen as one of the trade-offs you need to make for using a pure functional language. (I've yet to see many other languages with a type system as expressive as haskell).
Functions and lenses and module namespaces tend to alleviate the problem, not that it isn't annoying now and again. It does make some people go crazy though.
That's a shame, do we know why? is it just not finished being implemented? I found a page[0] that said at some point they had stalled on refactoring something.
That's a shame and the first I've heard of the delay. For me and the software that I write, this is a must have feature and I was very much looking forward to its release.
It seems to me that most of these issues could be solved at the syntactic level simply by allowing recordval.fieldname syntax. I truly don't understand why Haskell doesn't allow this - anyone have some insight?
This has been proposed, and it is still a good idea if one wants to make Haskell accessible to OO programers. It would require the function composition dot to have spaces around it, which is already fairly standard practice.
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).
The . already has whitespace-sensitive use for namespacing in Haskell, for example `Data.List.concat` is parsed differently from `Data . List . concat`
Then what is the type of (\x -> x.slot). It would have to be something like `HasField "slot" b a => a -> b` which is a significant increase in complexiy. It's not just a syntactic issue.
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.
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.
It can appear in the range (making up assignment syntax here)
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.
Should have said "if it can't appear in the domain, it can't appear in the range". Obviously if it appears in the domain it can also get into the range - a simpler example with real syntax is \x -> x.
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.
recordval.fieldname would have the same syntax as function composition func1.func2
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.
One could simply choose an alternate symbol, e.g. record~>fieldname (which is similar to C++). I admit choosing a symbol in Haskell is a bit harder, since most of the easy ones are already taken.
That was my intuition as well, but doesn't seem to explain why we can still use the syntax with modules (i.e., Set.map), unless the capitalized module name makes all the difference.
A quoted value is exactly what is written, for example in LISP:
(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.
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.
Mhyes, it appears that I misunderstood the precise mechanism of this lib.
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.
There is a problem. I know no other language, which manages to have at the same a type system as powerful, abstractions as insightful, as good a support for concurrency and parallel programming, an ecosystem as rich and which performs as well.
Usable records are just an icing on the cake, but it's the cake that matters.
Another way to solve this problem, is to allow multiple (nested) modules per file. A bit like OCaml. Then standard Haskell-syntax can already handle everything we need.
This article sounds a lot like the way that records are implemented for Scala (in Shapeless), which is a language that meets all your other criteria :P.
(Not that there aren't good reasons to use Haskell instead of Scala, but Scala has everything on your list)
Actually Scala is the language I moved to Haskell away from. It turned out to be too messy and bloated for my taste. The syntax and the type inference are massively inferior to Haskell. An absolute majority of Scala's advanced features are just copycatted from Haskell (typeclasses, STM, Scalaz, the majority of the Shapeless library that you mentioned). The promoted usage applies it's object-orientation mostly only to encode a modular system (e.g., the cake pattern). A lot of things are simply impossible to achieve due to Scala not being purely functional.
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.
Ceylon's type system is based on Java, so no, it is nowhere near as powerful as Haskell's type system. The above post didn't mention it, but I would add purity to his list. Ceylon doesn't have that either.
It's not, Ceylon's type system is way better than Java's, it's actually the best type system I've seen in a language. I wouldn't say Haskell's is significantly more powerful, if at all.
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).
Hmmm, perhaps my cursory skim of Wikipedia and the Ceylon site led me to wrong conclusions there. I'll defer a more detailed type system comparison to experts in the field. The big difference I see on my second pass is purity. Purity in Haskell is huge IMO. I would argue that purity makes Haskell's type system more powerful because a function's type allows you to know exactly what kinds of side effects it might have, or more importantly, not have. But I suppose one could debate whether that's a type system thing or not.
> 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.
I have very little experience with Haskell so I may be wrong about this - I gave up on Haskell because it didn't feel pragmatic, it didn't seem to make trade-offs that maximize productivity. Does purity (and effects of it on language design) really make the language more productive? Do people love Haskell because they are super productive with it or because they find it elegant etc?
Ceylon on the other hand really clicks with me more than the many other languages I've looked at.
No language as drastically different from the mainstream as Haskell is will feel pragmatic to someone who is unfamiliar with its paradigm. So I think it's perfectly understandable that you feel that way. It takes substantial effort to get out of the local optimum to find a better global one.
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.
Having looked into Ceylon and thought about this a little more...
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.
This shouldn't be a problem I think. There's not a reason to cast to Set<Object> and then look for employees there. If a function wants to accept a set of let's say emplpyees and ints, it would have a Set<Int|Employee> argument.
> imperative and side-effect programming (which often the most readable and understandable way to write code)
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 :)
> use a language which solves this problem by design from the beginning
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?
While the annoyances are real, they are just that: annoyances. If you change language every time a syntactic feature (or lack thereof) irks you, you're never going to get anything done.
In which case you still aren't going to get anything done, but for other reasons (lack of libs, playing with the language instead of doing anything productive, advocating for Lisp instead of doing anything productive, etc).
> In which case you still aren't going to get anything done, but for other reasons (lack of libs, playing with the language instead of doing anything productive, advocating for Lisp instead of doing anything productive, etc).
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.
It's not like having your own homegrown dialect of non-idiomatic code is a good thing anyway. On the other hand, the linked library looks more like a working proof of concept for a solution to a longstanding problem, with the goal of eventually merging it into the language.
What makes you think this is not what happens in other languages? 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.
>What makes you think this is not what happens in other languages?
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.
This can be true, but with discipline you can use superior technologies that may not quite be as popular as Java/C/Javascript/PHP but have all the libs needed for your use case.
> I think I have a radical idea: use a language which solves this problem by design from the beginning. Much simpler.
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.
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.