Hacker News new | past | comments | ask | show | jobs | submit login
An alternative front end for Haskell? (gilmi.me)
116 points by amalinovic on Oct 7, 2023 | hide | past | favorite | 62 comments



Most of the comments so far are negative, so I'll add something positive. One thing I love about the Haskell community is how they are always questioning their assumptions and genuinely seeking the best way to do things (often drawing upon or contributing to computer science research). The core concepts behind Haskell are clean and simple (essentially something between System F and System Fω), but there's a lot of baggage on the surface that obscures that underlying elegance. We should encourage people taking an introspective look into their tools and asking how they can be better, even if certain proposals are unrealistic or controversial.

I think the author is just writing down their opinions (which are worth discussing!) and not seriously trying to start a new GHC frontend. Personally, I think Haskell is approximately stuck in a local maximum, which can only be escaped by embracing dependent types (which are actually simpler than where Haskell has been heading) rather than building increasingly complex approximations of them. Once you try a dependently typed language like Agda, Lean, Coq, or Idris, it's hard to go back to the complexity of having two (or more!) separate languages for types and programs.

Regarding the proposals in the article, the most interesting to me is (2), although I'm not sure about some of the specifics. In general, I think Haskell needs a way to graduate (or retire) language extensions, rather than having them accumulate unboundedly. It's harder to talk about Haskell when everyone is using a different flavor of it.


> Personally, I think Haskell is approximately stuck in a local maximum, which can only be escaped by embracing dependent types

To give a counter point, there seem to be lots of small wins that aren't taken, possibly in part because people seem to wait for the big ideas.

Take partial functions like head: You can do what Haskell and Java do and throw an exception. Or you can have dependent types and statically prevent the function from being applied to an empty list. But the obvious and easy solution in the current language would be to return Maybe, which isn't done because there's a feeling that it's not a big enough step to be worth the effort, and dependent types will eventually solve this anyway.


> But the obvious and easy solution in the current language would be to return Maybe, which isn't done because there's a feeling that it's not a big enough step to be worth the effort, and dependent types will eventually solve this anyway.

That's not why it's not done. listToMaybe already exists[1] and you can't change the type of head without breaking everyone's code, so head in the next version of base will come with a warning[2] and that's about as much as you can do whilst still maintaining backwards compatibility.

[1] https://www.stackage.org/haddock/lts-21.14/base-4.17.2.0/Dat...

[2] https://github.com/haskell/core-libraries-committee/issues/8...


That's an interesting counterpoint, but I think it's a bit of a different tradeoff. Adding, say, dependent types, can be done as a gradual process with opt-in (via language extensions) and "only" requires heroic effort on the part of the compiler writers... whereas changing 'head' requires 10000i Hackage packages to update their code.

This is all tied into how inflexible the base/Prelude story is currently, etc. If the Prelude were a fully independent library where you could just depend on any old version you like, then there'd be no problem changing the signature. (Other than the usual diamond dependency problems). Of course you can choose NoPrelude and go from there, but then you're already in a place where changing 'head' doesn't matter to you, only the maintainer of your alternative Prelude.

People are working on both of these aspects, and that's got me really excited about where Haskell is going these days!

(I've always loved Haskell as a language, but there have been undeniable ecosystem issues.)


In cases when head is used though, you clearly just want to unpack the first element of a list. So if you replace head with a function that returns a Maybe, all you’re going to do is pattern match on its result to check if it’s a Just. So I don’t see a benefit over matching x::_ against the list straight away.

(I do see how a function like that is useful, but I don’t think it’s a good replacement for head.)


> all you’re going to do is pattern match on its result to check if it’s a Just

Something is eventually going to pattern match on it. But it might well be some handler way way up at the top of my program, and all I want to do right now is `map` and `bind` as normal.


Then you're clearly not using head. For this you can use listToMaybe.

Don't change the meaning of an identifier to something meant for a completely different use. The alternative change involving dependent types or liquid types doesn't break existing code. Changing head so that it returns a Maybe breaks code, because its use case is different.


I'm not using head, because I know Haskell well. But someone new to the language is never, ever, ever going to intuit that you get the head of a list with `listToMaybe` and `head` actually means `unsafeHead` and should almost never be used.

> Changing head so that it returns a Maybe breaks code

Of course. So did `Applicative`. So did `Foldable` and `Traversable`. So will removing (/=) from Eq, whenever that actually gets merged - and that was imo a far, far less problematic wart than having partial functions in Prelude. Breaking changes need to be made very carefully and very slowly: simplified subsumption was terribly handled, if not an outright mistake. But refusing to make them at all is how you get C++.


> But someone new to the language is never, ever, ever going to intuit that you get the head of a list with `listToMaybe`

They don't have to. Pattern matching is one of the first things they will learn, so they can use that instead.

> and `head` actually means `unsafeHead` and should almost never be used.

They don't need to intuit it. The next version of base will have a warning on `head` telling them to use a safer alternative instead.

By the way, the Haskell community is currently struggling to accept that it should make fewer breaking changes, in order to become a more stable platform for industry use. We're definitely not going to start breaking more code!


Some people think that if you don't throw exceptions then you don't have to bother dealing with errors. ¯\_(ツ)_/¯


Liquid Haskell solves the head problem as well as DTs - and it's available as a plugin. You can literally use Liquid types as easily as any library.


I recently got into an argument with someone who insisted that since “undefined” could be considered “non-terminating” there was nothing wrong with the current behaviour.


I guess parsing the list into a NonEmpty would probably be considered somewhat more idiomatic haskell than returning a Maybe from head.


> I think Haskell needs a way to graduate (or retire) language extensions, rather than having them accumulate unboundedly. It's harder to talk about Haskell when everyone is using a different flavor of it.

That is what the standardization process is for. I don't think that the parties that could write a new Haskell standard have the time, resources, or bandwidth to work on one. That's why for now we're stuck with 98, 2010 and a bunch of extensions.


I really like the Haskell that goes easy on the dependent-type sort of stuff: and I say that as someone who has been on-call for Sigma (which is like, top 3 industrial use cases).

It’s just really, really expressive without trying to solve the halting problem via some unification failure mode.

Type classes and GADTs already give you 10x the canvas you can get without trouble anywhere else, and if you keep it tight, you have the tooling to really tune it up.

Idris is awesome, do that if you’re doing that. Haskell is changing all the time: it became a serious production tool: I’d really like for it to stay one.


Haskell's current dependent typing is expressive, but also painful to use and almost impossible to debug. The main thing I want out of dependent Haskell isn't more power (though I wouldn't turn it down), it's the ability to write type-level Haskell like Haskell instead of some early Prolog prototype.


> embracing dependent types (which are actually simpler than where Haskell has been heading) rather than building increasingly complex approximations of them

very much second that! haskell has band-aid type-level programming that is so awful, but sometimes so useful, that it's really hurting a lot. I would just really like to deprecate type-families and would like to see as a the first step some non-dependently typed type-promoted functions. There's a great, pragmatic, proposal I would really like to see implemented. I, personally, don't need dependent types thaaaaaat much but type families are so unergonomic and really a dead-end I think. It's just stupid.

it's sad to see that the dependent haskell progress is losing steam and, as it is my impression, getting maybe a bit lost in the weeds?


DTs aren't losing steam at all. There are people chugging along implementing them.


> I think Haskell needs a way to graduate (or retire) language extensions

That sounds like the Extension lifecycle framework proposal https://github.com/ghc-proposals/ghc-proposals/pull/601


Many of these address "paper cuts". Individually, they aren't huge issues. But lots of small annoyances can easily add up and make a language not fun to use, and especially punishing for beginners. That's why some language communities prioritize fixing such paper cut problems. The Haskell community mostly doesn't seem to.

Another minor thing that happens to annoy me very much is how everything about Haskell's syntax and standard conventions is so diff-unfriendly.

To see what I mean, I picked the first "official" example I could find. Here are a few lines of code from the one of the examples in the Haskell playground (https://play.haskell.org/ -- it seems to pick a random one each time you load it):

    data Visitor
      = Member Profile
      | NonMember (Maybe T.Text)
      deriving Show

    data Profile =
      Profile
        { name :: T.Text
        , birthday :: Time.Day
        } deriving Show
You see Haskell code like this all the time, and the (IMO old-fashioned) lack of an optional trailing comma practically forces you into something like this. But just imagine inserting another variant to Visitor before Member, or removing the name field from Profile!

A more practically minded language might allow a syntax like this:

    data Visitor =
      | Member Profile
      | NonMember (Maybe T.Text)
      deriving Show

    data Profile = Profile {
        name :: T.Text,
        birthday :: Time.Day,
    } deriving Show
Bamm, each line is identical, editing is easy, diffs are clean. TypeScript, for example, has something like this for union types. Haskell unfortunately doesn't.


This resonates quite strongly with me.

Back when I first learned Haskell, I strongly got the impression that the Haskell community just... didn't like programming? I kept running into endless papercuts like these, language extensions which were poorly documented and mutually incompatible but basically mandatory due to widespread usage, and libraries which were considered "top-of-the-line" but where anything beyond the happy path was considered an open research topic.

All in all, it felt more like a loose collection of unfinished PhD theses than an actual programming language. Which is a real shame, because it has quite a few excellent concepts which are a genuine pleasure to use. Unfortunately I think languages like Rust and F# are essentially killing any chance it has at gaining a mainstream foothold: they bring over enough of the good parts of Haskell into mainstream programming that it simply isn't worth having to deal with the bad parts anymore.


> language extensions which were poorly documented and mutually incompatible

I often hear complaints about mutually incompatible language extensions, but when I aske I hardly ever find anyone who can name two! Can you?


One thing GP may be thinking of is the multitude of deriving-related extensions. For example when DeriveAnyClass and GeneralizedNewtypeDeriving are turned on simultaneously, the latter often doesn't work due to GHC silently picking the former on a newtype when both could be used. This particular annoyance has been fixed by a new extension, DerivingStrategies. Until this, it's impossible to turn on both extensions in a single module and have some newtypes derive some classes using one and some using the other.

I can't think of anything else that could be incompatible. I would also disagree GP's claim that extensions are poorly documented. GHC is very well documented, including all its extensions.


I believe that is exactly the one I ran into.

One library required DeriveAnyClass, another library required GeneralizedNewtypeDeriving. I couldn't enable both into one file at once, and I couldn't split the definition into two files. In other words: you're screwed. DerivingStrategies solved this, but that came out years after the problem started.

As to the "poorly documented":

I believe at the time neither the docs for DeriveAnyClass nor those for GeneralizedNewtypeDeriving mentioned the issue even existed, let alone a potential workaround.

A lot of other extensions have documentation written for computer scientists, not for programmers who just want to use it. It was not unusual for me to end up enabling an extension because some library required it, opening up the docs and having literally zero clue what was going on, and just end up copy/pasting the example code and praying it worked.

Extensions like OverloadedLists are totally fine, but once you get beyond the "Syntax" section the docs quickly become unreadable for most people not actively researching programming languages. It's not unusual for the docs here to start with "more details can be found in the paper XYZ", and be filled with terminology which is neither explained in-place nor have a link to an explanation.

When I read docs, I quickly want to know 1) what it is 2) why it is needed 3) how I can use it 4) what the implications are. From my personal experience, the GHC docs simply do not provide me with these.


OK, fair enough that these two were incompatible, but I think your earlier statement is really misleading!

> language extensions which were poorly documented and mutually incompatible but basically mandatory due to widespread usage

Neither DeriveAnyClass nor GeneralizedNewtypeDeriving is basically mandatory, and this problem only existed between the introduction of the latter in 2015 and the introduction of deriving strategies is 2017.

It's definitely fair to say there's a lot of crufty stuff in the Haskell world (there's also a lot of crufty stuff in the ecosystem of any other language I've used) but I think you really overstated the case.


Personally I think of Haskell the same way I think of C++ in terms of language features. There are so many and so much to choose from that there should be one person in every company that has simply decided that the company uses this particular subset and that's it, pending very detailed and well-motived discussions in the future, but at a snail's pace.

In my particular case I would also prefer for that subset to be chosen with a conservative and considerably less PLT-enthusiastic mindset, i.e. bog standard `RIO` style that prefers imperative code working on bags of mutable values of different types when solving problems.

But yeah, Haskell is a wide language and that's why it's so hard to adopt. People tend to learn ostensibly the wrong things, oftentimes from people who've never actually written any real production code but like to theorize and hypothesize about "the best pattern for X" and so on.


> Back when I first learned Haskell, I strongly got the impression that the Haskell community just... didn't like programming?

This is such a peculiar value judgement, and for the life of me I can’t work out what leads you to think this way.

If you don’t like Haskell, that’s fine. You don’t have to. But people who work with Haskell are generally happy and productive, just like most other programmers in most other general purpose programming languages.


for the life of me I can’t work out what leads you to think this way.

I suspect what is being referred to is that many Haskell people have a very strong academic background and are much more interest in working on Haskell rather than using Haskell to write 'normal' programs.


I've heard this idea many times over the years, but it just hasn't been my experience.

Most of the Haskell programmers I have worked with are programmers. Incidentally, they like writing software in Haskell.


It does match my experience though. Most Haskell programmers I've met are either working at Universities or within research groups at large organisations.


> If you don’t like Haskell, that’s fine. You don’t have to.

That's the problem: I do like most parts of Haskell! It's a language I really want to use more often, as many parts of it are miles beyond most other languages. Just the fact that it has proper first-class support for tagged unions makes programming an absolute joy.

All the "introduction to Haskell"-level stuff made me fall in love with the language, and I basically wanted to rewrite all my code into Haskell. But then I started trying to actually write production-level code and engage with the larger ecosystem, and I basically drowned into a marsh of incomprehensible language extensions which were somehow required for pretty much every single library - and more than once I ran into severe project-breaking design limitations only after investing dozens of hours into the library.

There are plenty of languages I really can't give a rat's arse about, but I like Haskell too much to just ignore it. To me Haskell is the one that got away, and I am eagerly waiting for someone - like this project - to essentially create a "Haskell for programmers".


> I basically drowned into a marsh of incomprehensible language extensions which were somehow required for pretty much every single library

I also see this a lot, but do you really have to understand language extensions? These days one just enables GHC2021 as though it were the "language" and completely forgets that technically there are "extensions" behind it. Haskell (or rather GHC) has really suffered from trying to be a good citizen. It gates new features behind `LANGUAGE` pragmas and rather than saying "oh, that's very polite behaviour, let me just do some simple busywork to get access to the new features" people say "oh no, these language extensions are scary and confusing things". They're really not! Mostly they're just about removing unnecessary restrictions from the older standard.

> and more than once I ran into severe project-breaking design limitations only after investing dozens of hours into the library.

Could you give an example?


> do you really have to understand language extensions?

You do when your code doesn't compile and you're trying to figure out what the error message means, or when the library you want to use makes heavy use of it for even basic functionality.

> These days one just enables GHC2021

My experience was pre-GHC2021. I basically had to enable at a minimum 5-6 language extensions in every single file.

> Mostly they're just about removing unnecessary restrictions from the older standard.

Yeah, those ones are usually fine. I have zero objection to things like FlexibleInstances or DeriveFoldable.

> Could you give an example?

I believe I was trying to implement Central Authentication Service using Servant. However, that required returning a custom HTTP status code. There has been an open Github issue for this since 2017, but it seems to require basically rewriting the entire framework: https://github.com/haskell-servant/servant/issues/732

Looking back at it now Servant does have "ServerError", but that basically requires giving up all the advantages Servant claims to have and I believe it was not a viable option at the time. Looking at the timeline I was probably also on Servant 0.15, and there seems to have been a rewrite since then.

I vaguely recall running into a similar issue trying to interact with a database, but I can't remember the details of that.


Thanks for your reply!

I'm in a tricky position because I'm spending quite a lot of effort trying to improve the Haskell ecosystem, and although people report a lot of difficulty with language extensions I don't actually understand why. I suspect the problem is with something else, but it gets attributed to "language extensions" because that's the element that was most visible when the problem occurred.

Anyway, I don't mean to badger you, so feel free to ignore me if it's not a good use of your time, but I do have more thoughts and questions.

> I basically had to enable at a minimum 5-6 language extensions in every single file.

Sure, but I don't see a problem with that, per se. I routinely enable 20 language extensions in a single file! They're mostly just removing restrictions, as I said. It gets a bit tedious but nothing more than that.

> I believe I was trying to implement Central Authentication Service using Servant

Aha! Servant uses a style of programming (often called "type level" but I'm not really completely sure that's accurate) that I find rather inflexible. I would be very hesitant to use Servant myself, personally.

Is it possible that the problems you experienced were actually to do with the inflexibility with Servant itself, but the problems have been attributed to language extensions, since you were required to add them to make Servant work?


> All in all, it felt more like a loose collection of unfinished PhD theses than an actual programming language.

Yeah, to me Haskell is the experimental forefront of programming language theory and the most developed concepts get incorporated into the actual programming languages over time.


> the (IMO old-fashioned) lack of an optional trailing comma

I’ll note that in Haskell circles, the usual proposal is a leading comma, which would look like this:

    data Profile =
      Profile {
        , name :: T.Text
        , birthday :: Time.Day
        } deriving Show
The commas essentially end up like bullets — a style I thoroughly like.


Your syntax is incorrect, it should be

    data Profile =
      Profile
        { name :: T.Text
        , birthday :: Time.Day
        } deriving Show
(You can't write `,` directly after `{`.)

This is also why the commas are _not_ like bullets, no matter how you format them. The first one is always special.

I agree that somebody should make a language extension that allows what the parent posts says.

Doing this does not need a language split / new frontend; this is what language extensions do very well. If they are popuplar, they become part of a new (de-facto) standard, such as GHC2021: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/cont...

And there is precedent for this: There is one place where trailing commas for good diffability are already allowed: Module exports.

    module MyModule (
      fun1,
      fun2,
      main,
    ) where
Many language extensions exist to remove such "papercuts"; this looks like a good candidate.


The comment you're replying to refers to the fact that

    the usual proposal is a leading comma
You're right that it's not valid Haskell but that wasn't the goal here.


I understood "the usual proposal" as "how people usually propose to format current code when others complain about lack of trailing comma", not as a proposal for an actual change to the language.


GP is correct. I was mentioning this as a common proposal for syntax change (or, this being Haskell, a new extension). I did not mean to imply that this is current syntax, since of course it isn’t!


When breaking an expression into multiple lines, it is best to place an operator at the start of a line.


Tried with OCaml, multiple times, and they all withered away and practically everyone is back to using normal OCaml syntax.

(I'm not only talking about ReasonML, Reason, ReScript etc. There was a more officially proposed revised syntax too: https://caml.inria.fr/pub/old_caml_site/camlp4/tutorial/tuto...)


A plug for PureScript:

Haskell-like syntax, row polymorphism, nice interface for records. Warts removed from standard libraries. Everything is a total function, but otherwise names and type signatures will be very familiar. Strict evaluation. The primary compile targets is JavaScript, second most popular is Erlang. Excellent FFI.


These are by far not the biggest problems with Haskell and splitting the language will definitely not help.


It does seem like coffeescriptification. I would argue the way to tidy up Haskell is

1. decide on a decent non GHC extended standard. (maybe the last one was good enough? not been following)

2. build out a decent standard library to cover all the usual things from datetime handling to web serving without using GHC extensions

Then let everyone use that

Even better have a JVM/CLR type target for syntax experimentation. I think they have this already.


Just an opinionated bunch of changes. Some of them I really hate. Some of them I don’t care.

This should really by going though proposals 1 by 1. Not as a features-someone-likes.

Cons list syntax is the worst proposal


Instead of the cons proposal, should just make the `(:)` operator work on ListLike[1] instead of only built in lists.

[1]:https://hackage.haskell.org/package/ListLike-4.7.8/docs/Data...


> Remove tuples - use records instead.

Seriously? Mainstream languages are busy adding tuples because they make code so much cleaner when used appropriately. Leaving them out of Simple Haskell™ would be a big mistake, IMHO.


Yes this appears to be a silly suggestion, tuples are everywhere, especially as arguments for functions where it does not make sense to name the fields. They are also a structural type and records in Haskell are, unfortunately, nominal types. If Haskell records were structural (one would use newtype to make a nominal record), then tuples could be syntactic sugar for a structural record with field names 1, 2 etc. There is value in unifying them with records, but we still need them.


I like the change of $ to <|

Dollar signs are so noisy and opaque.


My biggest syntax gripe since starting out with Haskell is needing to introduce '$' at some point during a chain of '.':

    mySet

    toList mySet

    reverse . toList mySet

    reverse . toList $ mySet
The first three look pleasing to the eye, but the fourth is ugly. I want to write the third one, not the fourth.


Add parenthesis around function composition?


What about

    (reverse . toList) mySet
Or:

    let fun = reverse . toList
    in fun mySet


You can already never use $ and use <| instead. Nothing is making you use $


This is what F# uses (as well as |>, >>, and <<), and I like it a lot. It's consistent and intuitive pattern. Every time I read Haskell, it takes extra effort to read . and $ instead.


I do think that it would be useful for the community at large to move onto `&` more (as that is what `|>` actually is), together with `>>>` for forward function composition. Certainly I think it flows better with the rest of the language, which is predominantly read left-to-right and downwards.


Curious about the reasoning for removing type aliases? I don’t have an opinion on that myself


newtypes are strictly better. Basically 100% of the time a type alias leaks some API that's inappropriate for the intended usage of the type, and you can derive newtype methods if you actually want to do that.


Not strictly better. Type aliases are sometimes a nice thing to use.


Why not just go all in and start with Haskell 2, the way Python did with Python 3? Yes it's less convenient, looses interoperability etc., but Pythons experience shows that its doable and in the end it might be worth it.


A breaking chance in the frontend could be provided as a language extension. Some years later, it could be made the new default, and the old model would have to be explicitly enabled via one of the older omnibus extensions like `Haskell2021`. In the best case, the breaking changes are unambiguous and can be be automated via a rewrite tool.


Liskell was one.

It was Haskell with S-Expressions (Lisp like) as it's front end.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: