Hacker News new | past | comments | ask | show | jobs | submit login

> An example of this is the concept of resumable exceptions. During Semantic's interpretation passes, invalid code (unbound variables, type errors, infinite recursion) is recognized and handled based on the pass's calling context. ... Porting this to Java would require tremendous abuse of the try/catch/finally mechanism, as Java provides no way to separate control flow's policy and mechanism. And given Go's lack of exceptions, such a feature would be entirely impossible.

Not knowing much about FP, It'd be great to see a more in-depth article explaining this problem domain a bit more and showing some side-by-side examples of Haskell's specialized call sites compared to a Java try/except/finally solution (although Python would be a better procedural exception-based language to compare to)

One of the main reasons I don't care much about Haskell is because without any side-by-side comparisons of Haskell vs <insert procedural language> I don't understand what the Haskell advantages are, and I don't know when I'm dealing with a problem space where Haskell would help me.




I wrote a something[0] that demonstrates Haskell programs side-by-side with a Java program for a super toy problem. It goes on to explore more complex Haskell abstractions which implement the same simple program using less approachable techniques.

It is primarily meant for helping people understand how the abstractions work, rather than make an argument for when they are good to use, but it might give you enough background to understand the discussion around Haskell abstractions.

[0] http://reduction.io/essays/rosetta-haskell.html


Not Java or Python, but here’s some Ruby: https://www.honeybadger.io/blog/how-to-try-again-when-except...

The above article talks about exploiting Ruby's support for call/cc to do something similar.

A noteworthy difference: Ruby's support for call/cc is baked into it's runtime, while in Haskell you can implement call/cc as a normal library. This is done by leaning on Haskell's monadic "do" syntax and a suitable implementation of the Monad class.

http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Mo...

I'm rooting from my phone, so I can't type up a this-vs-that. But maybe that piques your interest and you can read up on Haskell and programming with monads.


I’m a bit skeptical of the difficulty ascribed to doing something like resumable conditions in a Java system.

You could have a per-thread singleton stack of handler objects. To raise a resumable condition you call a method on the stack that searches outward for an active handler to ask for a restart option and just returns it. If none is found an exception is thrown to unwind the call stack.

(Common Lisp has restartable conditions native to the language and this is essentially how it works. Conditions just don’t unwind the stack until no restart is available.)

Haskell monads can easily model other interesting control flow structures but this is basically just a syntax nicety of the do notation that lets you avoid writing nested lambdas to represent continuations. If you’re okay with heavy lambda nesting in Java you can do it all by just implementing whatever monad you like in Java—although the type system isn’t as glorious when it comes to higher-order polymorphism and inference.


This is trivial to do in most languages (including Java and Go) if you just stop to think about it for a couple of minutes. Just pass some kind of context object down the call stack (or make it available as a global — make it thread-local if you need). Then when an error occurs, just call this handler and voilà!

This is, in fact, pretty much what Haskell does. Except this handler is in a typeclass instance, and this typeclass instance is passed down the call stack, it just doesn't appear in the parameter lists but in the type signatures. It's a bit lighter syntactically, but ultimately it's the same thing.


I'm only a Haskell beginner, but I have solved similar task, with parsing that may fail, in CIS 194 course. The solution was based on applicative functors, and I really don't think that Java or other languages have anything like it.

Here's the lecture: http://www.cis.upenn.edu/~cis194/spring13/lectures/10-applic... and PDF of the assignment: http://www.cis.upenn.edu/~cis194/spring13/hw/10-applicative.... Here's my solution of it: https://github.com/golergka/cis194/blob/master/Homework11/sr...

I hope it's readable enough to illustrate the overall concept - but please bear in mind that I'm a beginner and probably have made dumb mistakes. If you have any criticism or suggestions, I would love to hear and learn from them: learning Haskell is the first time in my software engineering self-education where I wish I had regular contact with instructor or a mentor.


This guy wrote 3 really short blog posts about doing Haskelly-stuff in C++. [1, 2, 3]

They are pretty concise and [2] especially did a good job of making me understand some of the power of Haskell.

[1] https://www.syrianspock.com/functional%20programming/softwar... [2] https://www.syrianspock.com/functional%20programming/softwar... [3] https://www.syrianspock.com/functional%20programming/softwar...


I think it's hard to do just side-by-side comparison. Just imagine you just go back to 90s and convince C++ fans using Java.

They are all general purpose programming languages, just with some better/different design decisions to make things safer or relatively more/less expressive.

Similarly, the difficulty to explain Monad is Monad itself is pretty abstract and general. You can describe what it is, but it's hard to guarantee what the audience get from you. Someone says it can solve asynchronous programming issues, then someone will think it's something just for solving this kind of issues. Same as solving null pointer exceptions. After all, it's an abstract representation of sequencing computations with effectful context. But the last one is much harder to understand than the formers.

An equivalent thing is expressing the concept of the "variable" to a mathematical audience who never heard of a computer. They can definitely understand part of it, but not what you want to express.


The "Monad" typeclass in Haskell is a unification of a bunch of things that are treated separately in other programming languages. By "unification" I mean the same kind of thing that physicists mean when they say that Maxwell unified light and electrical phenomena, or that Newton unified physics and astronomy.

It is this unification that makes monads so powerful; one abstraction handles sequencing, exceptions, non-determinism, parsing, IO, transactions, asynchronous state machines and tons of other stuff.


i like to explain monads as the same sort of thinking process as a 'design pattern' in OOP. People who have done OOP would have heard of things like the visitor pattern. In OOP, patterns aren't unified in some class, but could be.

Monads (and type classes in general) are like design patterns in OOP, in that they encode a common use case. Except in haskall, you can encode this pattern into the language semantics.


>Similarly, the difficulty to explain Monad is Monad itself is pretty abstract and general.

A Monad is just a Monoid in the Category of Endofunctors.


For the non-Haskell readers, I should point out that this is just an old joke in the Haskell community. If you want it explained, see here:https://stackoverflow.com/questions/3870088/a-monad-is-just-...


damn... I missed "what's the problem?" haha.


This. What's so hard to understand about that?


Would I be correct in saying you just encapsulated the monad state type `this` in an identity `that` by creating a monad transformer out of `understand`?


> One of the main reasons I don't care much about Haskell is because without any side-by-side comparisons of Haskell vs <insert procedural language> I don't understand what the Haskell advantages are, and I don't know when I'm dealing with a problem space where Haskell would help me.

This is one of haskell's biggest problems. It's just enough outside of the normal flow of imperative languages (yet usable for the same problems) that you can't tell how much of an improvement it is unless you try it. Also, people who write haskell are more inclined to share lofty/abstract/interesting-to-other-haskellers code rather than your normal day-to-day code that is massively improved/safer and benefited from haskell's features.

I've mentioned this before, but one example of where it became apparent to me how much haskell had changed what I expected from language was non-nullable types. It's starting to be really common in languages now (typescript, kotlin, etc), but if you are used to writing imperative languages, the worry of nil/None/null is ever present, and a concept like Optional<T> is actually quite foreign looking. If you really think about it, it means that none of your language is safe -- none of your functions are safe because they said they wanted a String but you might have gotten a null that looks like a String to the typechecker and will blow up at runtime.

Another key improvement in haskell is the removal of class-based code-sharing (i.e. inheritance) -- the separation of behavior and data is really important, and most languages are starting to come around to this now (go w/ structs + interfaces, java w/ data classes, kotlin w/ data classes, rust w/ structs + traits), but haskell (and other ML languages) have been there for a while.

Yet another key improvement in haskell is the errors-as-values paradigm that is everywhere. If some function has a possibility of failure, then it should return `Maybe TheThing` or `Either AnError TheThing` (see how nice and legible those types are?) -- this forces explicit checks on failure and allows cases where there isn't a chance of failure (just `TheThing`) to speed ahead without nullchecks and be fairly certain. This actually pressures you into trying to sequester failure across your codebase -- you try to write functions that have signatures like `TheThing -> SomeArgument -> OtherThing` (see how legibile that is?), to minimize on the amount of `Maybe x` or `Either error x` you have to deal with -- this is often if not always good for codebases.

Maybe this is something I can help with, I write about pedestrian haskell a bunch, and I've been meaning to do a blog post on why haskell is better <your language>, something to really rustle the jimmies.

BTW, the quote about resumable exceptions is actually referring to a concept called a monad, which can be incredibly hard to grasp if you don't look in the right places (there are a lot of bad tutorials out there), or don't give your brain long enough to marinate in the concepts. If I were to take a stab at explaining it simply, in this case it's like a combination of exceptions-as-values (i.e. not go's approach, and not java's approach) and the value that is being passed around has enough state in it to continue stop, fix itself, whatever else. When something goes wrong in most imperative languages, you kind of get the hell out of dodge, and you lose access (usually) to whatever work was done up until the function boundary -- it doesn't have to be this way but it usually is.


> Also, people who write haskell are more inclined to share lofty/abstract/interesting-to-other-haskellers code rather than your normal day-to-day code that is massively improved/safer and benefited from haskell's features.

When Rust started getting flooded with the "web" crowd of ex-Rubyists and the like there was a lot of push back from the traditional systems people (for better or worse). But one of the benefits is that these guys are typically far better at communicating and selling languages to the general developer public.

I too have run into countless examples of these "beautiful" Haskell code examples but when it came down to doing real work I felt like I was left to either figure it out myself, try to connect a more abstract blog post to more practical applications, or left reading some auto-generated Haskell/library API documentation (75% was the last one).

Maybe Github and Facebook et al can lend some of these resources to teaching Haskell to the public and releasing well-documented libraries which set a standard for others to follow? It may have a high learning curve like Rust, but it's far from impenetrable for your average developer.


> When Rust started getting flooded with the "web" crowd of ex-Rubyists and the like there was a lot of push back from the traditional systems people (for better or worse). But one of the benefits is that these guys are typically far better at communicating and selling languages to the general developer public.

I 100% agree -- this is the crowd that brings the hype (for better or for worse). I guess it's another one of those life lessons, but projects need both types of crowds (and to be honest it's not like there's a strict separation, lots of people fit in both camps).

I think Rust is actually going to eat a ton of what could have been Haskell's lunch -- it's a great typesystem for the traditional imperative language crowd and awesome performance for the ML crowd, and a completely new paradigm of data safety that neither of those crowds had before. These days I struggle to choose Haskell, but have settled on Rust for "performance critical" things (I don't really write truly low level software so take that with a grain of salt), and Haskell for everything else.

> I too have run into countless examples of these "beautiful" Haskell code examples but when it came down to doing real work I felt like I was left to either figure it out myself, try to connect a more abstract blog post to more practical applications, or left reading some auto-generated Haskell/library API documentation (75% was the last one).

> Maybe Github and Facebook et al can lend some of these resources to teaching Haskell to the public and releasing well-documented libraries which set a standard for others to follow? It may have a high learning curve like Rust, but it's far from impenetrable for your average developer.

Hugely agree, but I think it's gotta be a community effort. FPComplete is out there doing stuff, and there are lots of individual bloggers, but Haskell needs more people writing "pedestrian" programs. I think it's one of the main ways of contributing to a language that is often overlooked. I don't have any numbers, but learn you a haskell for great good has probably lead to thousands of new haskell devs over it's lifetime, even if the information in it is outdated (and some consider it not a good starting point).

To compound all this, haskell also has a documentation problem -- the machinery is there but it often doesn't get written, or people don't include the "getting started" use cases. Most popular libraries are workable but some others aren't, so it's intimidating until you really start to see the types as sufficient for understanding.

Small shameless plug, I try to write about haskell and am in the middle of a post where I make a CountMin data sketch right now, I'm not quite done with it but hope to have it done this weekend. I feel in that way I'm at least doing something to help the haskell community.


> (and to be honest it's not like there's a strict separation, lots of people fit in both camps).

In my experience it always has to be both (a developer with good communication/marketing skills). Any non developer pushing a language or platform is always the wrong choice and will probably scare away more of the devs, who want to take specific code not just genetic benefits, than help. Too many “developer advocates” have rubbed me the wrong way.

Besides most of it is good web design, writing good newbie friendly documentation and guides, answering questions on HN/Reddit (which Jose from elixir is really good at).

Then once you get past the early adopter phase you need to convince the CTOs, who listen to their developers but also take a strong long term risk analysis when judging it. Including things like hiring and support for core libraries.

None of this will happen without the initial group getting drawn in. So hopefully we’ll continue to see more blog posts like above by Github giving their honest practical feedback and publishes libraries.


> Small shameless plug,

No point plugging if you don't provide a link for us! :-)


I hear you! I was going to try and get the CountMin post done this weekend and update this but I guess I gotta let it roll with what I have already done:

One of my better Haskell posts is a series on writing REST APIs. It kind of gets off the rails type wise as I try to get more and more clever, but I rein it in:

https://vadosware.io/post/rest-ish-services-in-haskell-part-...


Nice, I will have a read.

I think you need to get rid of the margin-left and margin-right styles on '.article-content pre' for '@media screen and (min-width: 989px)'. It pushes the code off the bounds of the page on my screen.


Thanks so much, I will get that fixed this weekend!


The selling point for me to get into Haskell was how much better it's lowest bar is than pretty much every other language I've used so far. It's not perfect but it has a very good effort to power ratio.

For example, I have a database with millions of unstructured, schemaless JSON documents. The documents are mostly similar but the schema is defined by Javascript code that has been maintained and modified for many, many years and so the documents have many, many edge-cases.

I've dared my team to write a JSON-Schema document that could validate our format. It would take a lot of effort to get that going. And it would be verbose and hard to work with: JSON-Schema itself is written in JSON and there exists no tool that can understand the types in the schema, validate them, etc until you run the program on some documents.

Instead I spent a few hours and wrote a type that loosely described some of the more common types I've come across in Haskell. I used the wonderful JSON libraries to parse documents from our database using my small, limited type in a test suite. It failed initially but the type system pointed out where it was failing and why. So I added more cases to my type, improved the parser, until I could parse a handful of real-world examples.

From there I wrote a tool that scans the whole database, collects the parse results, and displays the top N parse failures with example documents to add to my test suite. I use the test suite to interrogate the parse results, add more cases to my type, extend the parser, etc, etc...

I've spent very little time working on this and got my tool successfully parsing almost 90% of the database. When I add new examples the type checker guides me as I interrogate the new case, fix the edge cases, and get the tests to pass. Once I can parse 100% of this database I can start writing a tool to migrate my messy data structure into a more clean, consistent one with a more simple parser and prove that the migration is total. And I suspect it will take even less effort to do that.

I'm not even leveraging anything more advanced than ADTs, type classes, and functions. For very little effort Haskell has given me the ability to solve valuable problems. Problems that scared other developers away using other languages.


This is an awesome usecase and a really good use of the expressiveness of haskell -- if your org has an engineering blog, please write about it I'd love to read more.


> changed what I expected from language was non-nullable types

I got the same revelation from the a lot more conventional [] looking Crystal, which don't solve it through optionals but through union types. Exposure to that kind of type safety to enforce non-nullability is really a watershed moment.

[] For values of convention that look like Ruby. Not everyone think that look is conventional enough.


Optionals are union types: Optional<A> is the union of Unit and A.


It's interesting that any time I read about Haskell, I realize most of the features can be found in other languages.

Functional programming and lazy evaluation are common in Apache Spark ("analytics engine for large-scale data processing."). You cannot write a good pipeline if you think in terms of imperative language.

Non-nullable types can be found in Java (@NonNull) and in C++ (references). C++17 got std::optional type.

We have languages without inheritance, like Go, Rust and so on.

Errors-as-values are pretty common as well (C++'s boost had boost::error code for a while now, and of course there is Go again)

Even monads find themselves in other languages -- using org.apache.spark.rdd.RDD is pretty close to IO monad.

I find this an unfortunate downside of many Haskell tutorials -- they often claim there are unique features that are present in Haskell only, but on the closer inspection, it turns out those features are present / can be trivially added in many other languages as well


Hey that was kind of my point -- but I think you have it in reverse, Haskell has had a lot of this stuff for a long time (as in most of them since it's inception), and it's trickling down to other languages now.

But to make some concrete counter points:

- Apache Spark is not a general purpose programing language (you're totally right about FP and lazy evaluation being important in DAG-land of course)

- "Non-nullable types can be found in Java", yeah except them being the default is the big innovation, along with the recognition of the problem, and facilitation of the worldview that recognizes the issue. Optional didn't show up until a few years ago (Java 8?), first class functions weren't a thing without subclassing till around then too, Function references, Functional interfaces, etc. I'm less familiar with C++ and it's commendable that it's adopting new things and people are moving forward, but it's basically the gold standard of footguns with type-system scopes attached (again I don't write C++ on a daily basis and haven't felt just how much better the new editions are).

- Go and Rust learned from Haskell, Rust heavily so. BTW these days I'm more and more of the opinion that Rust is the one more worth praising of the two

- What go does is kind of error as values, but it's also kind of not -- I mean a near complete lack of use of exceptions at all. The distinction is subtle, but coding to always handle the error case (because it is the result) is different from having a sometimes-present error code that you sometimes check.

- Again, you're right that Monads are everywhere -- Haskell didn't invent the concept, but it is one of the places you can go to see it actually used functionally and learn from what people are doing with it (never mind all the novel papers).

Haskell is one of the few places that all these features come together to form a coherent whole.


> Functional programming and lazy evaluation are common in Apache Spark

Spark ain't a language, it's an engine. Anyhow, you could make the point for lazy evaluation in stream libraries in many languages. It's not really comparable to having this as a first class citizen in the language, just like Guava didn't make Java7 equal to Java8.

> Non-nullable types can be found in Java (@NonNull) and in C++ (references). C++17 got std::optional type.

The reason people talk about it isn't about having non nullable types, it's about not having nullable types (or, at least, not having them as a default).

> We have languages without inheritance, like Go, Rust and so on.

I don't believe not having inheritance is a language feature. People may say that inheritance was a mistake and that languages without it are better off, but you will rarely hear about no inheritance being a feature of a language.

> Errors-as-values are pretty common as well (C++'s boost had boost::error code for a while now, and of course there is Go again)

Haskell has an error system. Errors as values are a pattern enabled by other things of the language (ADTs, functors/monads), but not having errors is not a Haskell feature. Elm would be a better example of this.

> Even monads find themselves in other languages

See above point about first-class things in a language.

Your analysys about features is misguided because languages aren't things that can be compared feature by feature. They are a coherent set of things that produce a specific dev experience. Haskell is offering a specific experience that people enjoy, and therefore, people talk about it, and some other languages tend to adopt some of the features in an attempt to reproduce the experience.


This is a catch-22 of asking people to explain the advantages with simple side-by-side examples. If you want an example of something truly unique to Haskell, we'd have to talk about e.g. using the cataM function in a kind-polymorphic way. But you'd have to get a certain depth into the Haskell mentality before you could understand why that's useful or valuable. So we talk about the simple examples - but lots of languages solve those simple examples with limited/ad-hoc/special-case versions of things that Haskell does with more powerful general features. https://philipnilsson.github.io/Badness10k/escaping-hell-wit... has some examples of this phenomenon.

Apache Spark could probably never have been created without using the only other mainstream language with higher-kinded types (Scala) - now that the design has been proven a lot of it has been rewritten in a verbose Java style, but I doubt the work to come up with that design could have been done while thinking solely in Java.

Working without inheritance is only practical if you have typeclass derivation. Rust and Scala approximate this with macros. I don't think any other mainstream language has that functionality at all.

Non-nullable types and errors-as-values are only practical if you have higher-kinded types. (Rust has an ad-hoc macro that works for a handful of error-as-value types, but you can't write the general monadic library functions that you'd want to work with them properly).

Haskell is not entirely unique, but it's pretty close. The only other remotely mainstream language that has the combination of higher-kinded types and typeclass derivation (ish) is Scala, and even then you'll eventually get bitten by the lack of kind polymorphism.


There are no tutorials that claim these features are unique to Haskell or that they cannot be added to other languages.


> > During Semantic's interpretation passes, invalid code (unbound variables, type errors, infinite recursion) is recognized and handled based on the pass's calling context. … And given Go's lack of exceptions, such a feature would be entirely impossible.

Nonsense! It’s completely possible — you just have to be willing to abuse a few of Go’s features in order to do it.

First off, Go does have a standard calling-context (i.e. dynamic context) mechanism: context.Context[0]. All you need to do in order to track stuff in the calling context is stash an object in the … context.

Given this dynamic context mechanism, you next need a way to represent handlers. That’s easy: a handler is a function which takes an exceptional condition and either handles it or returns; handling consists of a transfer of control. Fortunately, Go has a control-transfer primitive as well: panic. So to handle a condition, a handler just panics — something higher up the call stack can use defer to catch the panic and continue execution.

That leads to the next component necessary: a way to provide resumption points, or restarts. A restart is just a function which is invoked by a handler, potentially with arguments, and which when invoked continues execution from its establishment point. This can be done with defer.

So it’s perfectly possible with tremendous abuse of Go’s panic/defer mechanism, no different from Java.

See this gist: https://gist.github.com/r13l/2911f93cbe66fb4ed50f9d9eb1eb252...

Honestly, I don’t even know if I’d call it tremendous abuse, although it is somewhat abusive. Abstracted in a library, it might even be somewhat useful.

It’s off-the-cuff — I haven’t fully considered the semantics of the different context objects being passed around.

0: https://golang.org/pkg/context/




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: