Why Learn Haskell? (2018)
crypto.stanford.edu
crypto.stanford.edu
In short, it's a way to restrict the data types to such a degree that the type checker can do compile-time checks of array sizes and other interesting things, as those aspects are directly expressed as distinct types depending on each other. The principle requires a pretty powerful type system and is cumbersome to use, but I found it fascinating regardless.
I had mainly used Scott Wlaschin's site F# for Fun and Profit, and MS sites, for F#.
Edit: Also had looked at OCaml, via Real World OCaml, and also much earlier, via INRIA / O'Reilly sites / books / software.
F# may be based on OCaml.
Both resources are free to read online.
https://gitlab.haskell.org/ghc/ghc/-/wikis/dependent-haskell
Writing good Haskell always seemed harder to me than something like Python. The amount I'd have to think before beginning to type was a lot, but it always seemed worth it, as there was always potential always for a beautiful solution to any problem.
As a result, before I write code in any production language I give a little more thought to what I'm about to write, which I think ends up with a better result.
Heck, even if Haskell just gave a developer an appreciation for basic functional programming, that would be something that will benefit them for years in my view.
Also, since Haskell has a lot of advanced type system features, whatever feature that gains traction in other languages (traits in Rust and Swift, optional chaining, async/await) I find much easier to learn as they are usually specializations of more general concepts in Haskell.
And as someone who also studies mathematics, the connections between functional programming and areas like logic, order theory and category theory run deep—programs can too become objects of mathematical study, with their correctness proved and refactoring sound.
[0] with exceptionally rare caveats
In addition to expanding my thinking and making be a better developer in other languages, I think Haskell has made me expect more of programming languages. In a positive way. Things can be better than they are. That doesn't mean things should be Haskell. But they can be better.
I'm still not OK with monads though :-)
I think these concepts translate to other languages fairly well, and are easy to learn in Haskell:
- Newtypes (Languages like Kotlin/Swift have support for them, and you can kinda get close with lots of classes in Java)
- Purity (can write “pure” code in most languages)
- Algebraic Data Types (help with thinking about modeling data, works in eg TypeScript)
- Maybe / Optional types (Now very popular in other languages)
- Parser Combinators (work in eg JavaScript)
However, I think there are still some extremely interesting ideas that can only be implemented in Haskell and some related languages (e.g. Joy) which may prove very useful.
One can see the glimpse of that in Compiling to Categories [1]. In general, programming in a point-free way may lead to lots of advantages we don't understand well yet [2-3]. I think, for example, synthesizing point-free programs should be a cool path towards AI.
[1] http://conal.net/papers/compiling-to-categories/
I’ve used Haskell in production (not a very big service but anyway) and while I enjoyed it, I think that it was at the cost of my employer. Spending my time studying about lambda calculus and suffering through slow compile times of GHC isn’t a very rewarding experience (for me personally). IMO I’m still a better programmer for knowing the concepts of, but not by much. It’s a field bigger than Haskell and IMO writing production Haskell code only skims the surface of lambda calculus/theory.
Never used it in production either but I did use Haskell to ace some of my advanced computer science course work with less time and less lines of code than my peers.
... aren't those very basic things that are taught in the first years of any comp. sci. university ?
Is this like the curse of teaching monads? Once you know them you lose the ability to teach someone else about them?
> If we sincerely ask "why learn Haskell?", then we wind up learning Haskell!
No. We wind up doing a search for "Why learn Haskell" and unfortunately this article might pop up. And if we don't see a benefit or pain point being removed we aren't going to learn it.
They don't even make an argument for why. They just jump into a philosophy lesson on logic. That's not an argument for learning Haskell and is completely tangential.
The basic rules of copywriting requires that you understand your target audience and make a case for how their life will better.
> The gold standard is a logical reason.
LOL. No, it's not. The gold standard is an emotional reason. Perhaps this is why Haskell has not taken off. What pain point does it solve or what benefit does it give you?
For me personally, it has expanded my thinking and allowed me to write clearer and more concise code in other languages. It has allowed me to think in terms of new concepts that increase the reach of what I am able to do. Its type system provides for safety as well as making the intent of programs clear. Pattern matching allows you to specify your intent much easier and conveniently than the typical imperative approach. The compiler is your friend and reminds you if you fail to take into account all possible scenarios (total function). The syntax may seem strange at first, but I have come to love it and wish other languages had adopted such a clean syntax devoid of visual clutter.
The gold standard reason for me is that I am able to tackle, think, and solve higher levels of complexity than are possible in other languages. I am able to express myself much more fluidly and I am able to reuse code to a much higher degree.
Maybe the emotional reason is a warm fuzzy standard?
It's as if SICP were redone in Haskell in the 21st century.
Haskell has warts, as do all languages. Something that's tedious in one language might be a breeze in another. In Haskell, most of the baggage I've seen is around
- Cabal: seems the best advice is to just start with Stack - Delayed exceptions: due to lazy evaluation (which is a really great feature once you get used to it!), the site where you trigger an exception may not be anywhere near the actual exception-generating site. For example, if you call head on an empty list, then return that as a result only to have it be used far later, it will go "boom" at some later time. This is also true for I/O things: perhaps the file I/O way deep down in some thunk was closed due to an error, so some code that is not equipped to handle errors gets an exception.
Michael Snoyman has a great series of blog posts about this [0], i.e. "Haskell the Bad Parts 1-3". Most of this is managed by not using some of the defaults or with practice. Just be aware that like any language, there will be areas that may not have the greatest behavior.
That said, I think these confusing behaviors are very limited in Haskell, much more so than in C++ for instance.
I can recommend the https://haskellbook.com as an introducing resource.
If you want to dabble with something simpler/cleaner than Haskell, try Elm. It is just for browser apps, but extremely powerful and beginner friendly. You do not even have to install anything when you use this online tool: https://ellie-app.com/new
As a counterpoint, I found that book unbearably boring and abandoned it. I continue to be interested in Haskell so I may eventually pick it up again but I’ll skip (or at least skim) the first chapters.
I followed the book’s development (written by a programmer and a novice) and had high hopes for it, so it was a great disappointment when I got to read it.
There is not one Haskell, there are many flavors of Haskell, and every file can be a different subset because extensions can be toggled by file ( `{-# LANGUAGE <Extension> #-}` ).
My second biggest wart is the heterogeneous nature of the ecosystem due to lack of established paradigms, and the heavy use of complex type system abstractions in some libraries (plus the common lack of good documentation).
I love Haskell as a language for the learning experience, but I would not use it in a professional/production environment.
There's finally an effort to update the Haskell spec and many of the most common extensions are slated to be included so we won't have to use so many for "common Haskell" soon.
That's great to hear!
But in the future it would also be great if Haskell took a route similar to Rust.
In Rust, extensions (called features) only work on the nightly compiler. On stable and beta compilers, they produce an error. Only once they are officially accepted and finalized will they work on stable.
That gives both users and compiler/std lib developers the freedom to experiment, but prevents a proliferation of a complicated feature matrix and centralizes the ecosystem on a coherent language spec.
This has worked out really well. In the early days many users were stuck nightly because of essential features, but by now libraries are expected to work on stable.
Haskell the language is defined by a specification called the Haskell Report. The last one was Haskell 2010 [0].
The news is that the next version of this report will incorporate advances from several GHC-specific language extensions.
How useful this is in modern Haskell remains to be seen. As far as I can tell the de-facto Haskell compiler is GHC. Hugs is unmaintained and UHC and LHC are mostly experimental or in-development. However without a spec we can't get innovation from alternative implementations of the language.
I haven't touched it many years (save for the occasional amending of my xmonad conf) and would need a lot of time to get back to reading it properly, let alone code in it.
Still, I'm very happy I did that and look back at it fondly. It got me grokking the functional mindset and paradigm that still influence the design choices I make and the code I write heavily.
Today, I'd push for learning Idris instead, or maybe using Haskell as a brief intro to Idris. While young, it's a lot more approachable and makes working with statefulness and IO a lot more intuitive, without sacrificing purity.
A "pure" function is one whose result is simply based on its inputs, no dependency on external state/IO etc. Same inputs, same outputs guaranteed.
These types of functions, are really easy to reason about and unit test. State is often messy.
In OOP you try to limit the scope of state, private variables inside an object can update its value but can only be directly accessed from inside the object. But still those objects are inherently stateful. In order to understand an object, how you can use it, whether it is working correctly, you are still forced to understand where/when it is in the timeline of updates.
Purity means that if you understand a variable or object in one place in your code then you understand it from anywhere in your code. You don't need to know how it was used before because it can't be changed. If it were changed then you would know because it would now be a different variable or object.
I would further add that in day-to-day code, I don't necessarily write "pure" functions. I use a strategy based on my experiences with Haskell, but spiced with a bit more pragmatism. For any significant bit of code past a couple of screens or so, I almost always have a clean separation between the "business logic" code, and the "IO" code, such that I can plug different "IO" codes into the business logic code trivially.
The business logic code is not technically "pure", in that it calls out to IO code freely. But it means I can swap out the IO code for testing, and drive the business logic with any input I desire in the process. It also lets me test the IO code directly if that is useful/necessary/desirable, without the business logic getting in the way, which it often does!
Theoretically, you could transform this to "pure" code, by gathering everything into one big data structure in the IO code, then feeding it to the business logic, but this often comes with a lot of inconvenience and even performance issues (like gathering expensive things you might need but not using them).
One of the things that Haskell can "put into your fingers" is a sense of what code does IO. Even if you nominally know, you probably don't instinctively know if you've never used a language that rigidly forced you to be correct. It is a common experience for quite a while in Haskell to write a pure function, that calls a pure function, that calls a pure function... that, err... needs to read just a little bit from a file according to the way you've structured things. Whoops. And you learn to restructure things to move the reading back "up" in the code to constrain the purity, even though it's just a "little bit", and over time you get better at not making the mistake in the first place. If you are not trained by Haskell, it is really easy to end up thinking "oh, it's just a little bit of impurity... it'll be fine"... and maybe, in fact, it will. But this still starts to add up. I still write "impure" code... but I do so much more carefully now.
> An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior. This requires that the expression be pure, that is to say the expression value must be the same for the same inputs and its evaluation must have no side effects.
> In mathematics all function applications are referentially transparent, by the definition of what constitutes a mathematical function. However, this is not always the case in programming, where the terms procedure and method are used to avoid misleading connotations. In functional programming only referentially transparent functions are considered.
> The importance of referential transparency is that it allows the programmer and the compiler to reason about program behavior as a rewrite system. This can help in proving correctness, simplifying an algorithm, assisting in modifying code without breaking it, or optimizing code by means of memoization, common subexpression elimination, lazy evaluation, or parallelization.
> The concept seems to have originated in Alfred North Whitehead and Bertrand Russell's Principia Mathematica (1910–13). It was adopted in analytical philosophy by Willard Van Orman Quine.
I wonder if there are any functional languages that aren’t weird? Like they call the first element in a list / the other elements “list[0]” and “list[1..]” instead of “car” and “cdr”? Bonus points for a C-inspired syntax rather than an abstract-mathematics-inspired syntax
For now I just write Rust and Python using functional-style design (const inputs, no side effects, etc), but I feel like I’m missing out...
Have you tried Elixir?
I had been doing Ruby for a long time (also Java and Scala before and after that, now Python).
Gave Elixir and Phoenix framework a shot a month ago and it's a breath of fresh air: great language, outstanding tooling.
I've also done a bit of Haskell, OCaml and PureScript within my personal projects. I do miss having a state of the art typing in Elixir, but there is enough in Elixir to keep me happy:
* immutability everywhere
* ridiculously low latencies (not by HFT standards I guess)
* LiveView
* ...many other goodies that come with BEAM
* Tooling
edit: formatting
[Gleam]: https://gleam.run/
As far as I understand its serious enough but quite young.
And I used to be the python guru at the previous company and made a whole lot of language evangelizing etc, I am afraid there is no way back for me.
In commercial context:
* Of strongly typed ones only Scala (with [shapeless]). Can reluctantly throw in Kotlin as well for it's amazing structured concurrency.
In non-commercial context:
* Went through a few chapters of [Software Foundations] doing Coq proofs.
* Worked through most of the [Types and Programming Languages] (writing typecheckers in Ocaml)
* 3 services in Haskell (1 on Scotty, 2 on Servant). Loved persistent+esqueleto for the ORM layer, disliked Opaleye.
* 2 projects in PureScript (1 with Halogen, 1 with React bindings).
* 1 project in ReasonML (Ocaml).
-
> I am afraid there is no way back for me
I see where you are coming from. In my case I can alternate between "I want all invariants properly expressed and checked" and "I just want to ship that barely-working piece of junk and iterate on it". I learned to adjust depending on organization needs. IMO, for many orgs, especially startups/scaleups, the latter is often the more fitting way. With that in mind, I'm willing to trade the guiding hand of great type systems for other productivity aspects (amazing runtime and cohesive web framework in Elixir's case).
[shapeless]: https://github.com/milessabin/shapeless
[Software Foundations]: https://softwarefoundations.cis.upenn.edu/
[Types and Programming Languages]: https://www.cis.upenn.edu/~bcpierce/tapl/
Since functional languages are based on the mathematical idea of function application, this is a weird request. What would a C-style functional language even look like? Most functional languages have syntax for doing a imperative-style sequence of assignments before producing a result. Beyond that, the risk is that C-style code will make the programmer think that the language itself is like C, Java, or other imperative languages - which fundamentally, it wouldn't be.
Well, imperative languages are based on the idea of a turing machine, but they look nothing like one.
In what way? I wouldn't count executing sequential instructions as a "the idea of a turing machine", and no major imperative language has programs which have only finitely many states along with an infinite memory space that is both code and data.
I've not learned its Erlang base, so I can't tell my Erlang from my Elixir, but... in Elixir you can Enum.at(list,n), which with:
at = &(Enum.at(list,&1)
Could simplify into:
at.(n)
If that doesn't suit I'm sure some equally simple such thing could create exactly what you're after. (As I understand it, various kinds of metaprogramming are an intended base element of the language itself.)
Haskell's syntax is actually quite minimal by comparison. Scala type signatures, in particular of library functions, can be a beast to understand.
Also, because many Scala devs start using it as a sort of "better Java", spaghetti code and imperative-style messes are relatively common. This doesn't happen with Haskell, because nobody starting Haskell tries to write Java-like code with it.
That said, I do like Scala better than Java!
One reason is that second and third and such are oriented toward sequences. But sequences are often generic. In TXR Lisp, I have [x 2], so why would I ever write (third x)? It's verbose, like using Roman numerals instead of Arabic.
Now if I'm processing tree structure, I know that is made of conses. So (caddr x) makes sense.
Part of the reason it makes sense is that when we are processing tree structure, such as code syntax, we cannot just evaluate (caddr x) out of the blue. We can only do that if (cddr x) has been confirmed to be a cons cell: (consp (cddr x)). The syntax could be bad. It could contain the dotted notation in an unexpected place, or be missing required arguments.
And so this makes no stylistic sense at all:
(when (and (consp x) (consp (cdr x)) (consp (cddr x))
(third x))
We want this: (when (and (consp x) (consp (cdr x)) (consp (cddr x))
(caddr x))
It is also easier to read and verify. We know caddr is right because it just adds an a to cddr, the last cell which was tested.There is an impedance mismatch between validating (cddr x) and then extracting (third x), which isn't there when (caddr x) is used.
Anyway, a lot of that kind of code is avoided by pattern matching.
(when-match (@nil @nil @elem . @nil) x
elem)
That also avoids traversing the structure multiple times. Unless the compiler is clever about doing CSE between these functions, (cddr x) starts scanning at x.Out of curiosity, could you give an example of what you find "abstract-mathematics-inspired" of Haskell's syntax? Is it just the one-letter identifiers (which is more standard practice than syntax), the symbols for function names (which remind me of C++'s "<<" and the like) or what?
My own personal opinion is that Haskell's syntax has some pitfalls with indentation, but other than that it's not particularly difficult. It's just not C-like, but that's a separate issue.
Similarly, Lisp-like languages have barely any syntax at all!
car and cdr are remnants of an ancient instruction set (https://en.wikipedia.org/wiki/CAR_and_CDR)... first/rest, head/tail are better. Actually doesn't Haskell use head/tail for lists? Except even there what is in the prelude is busted since head/tail are partial.
(first '(a b c d)) returns 'a
(rest '(a b c d)) returns '(b c d)
(third '(a b c d)) returns 'c
(nth 0 '(a b c d)) returns 'a
(nth 1 '(a b c d)) returns 'b
(last '(a b c d) 2) returns '(c d)
I find these quite intuitive, and after a bit of use one gets accustomed to Lisp's three ancient functions: car, cdr, and cons. car is the same as first and cdr is the same as rest.In comparison to handling lists in more modern languages, Lisp seems overly verbose and a bit cumbersome. To be fair, there has been over half a century of evolution in programming languages since Lisp was originally designed.
Early Lisp was an alternative to programming in Fortran II, Fortran IV, or Algol 60. These are very primitive languages with significant hurdles for programmers trying to do non-numeric computations (e.g. AI programs). Lisp's underlying memory model of garbage collected cons cells gave it so much flexibility compared to Fortran's fixed length arrays containing only numbers. Fortran had no dynamic lists, no dynamic vectors or slices, no dictionary/hashmap types, no records or structs, no variable length strings, no sets, no tuples. Assembly language often appeared as a reasonable alternative.
Here are a few Fortran if statements appearing in a popular programming book of the 60's (I still have it on my bookshelf):
IF (x - 2.1) 40, 40, 30
This if will always jump to either line labeled 40 or line labeled 30 of the program.Here's another:
IF(I.GE.20.AND.I.LE.42.AND.J.GE.20.AND.J.LE.42) GO TO 702
Lisp's cond (the special form used like if) may seem odd now, but compared to the other languages of the time it was so much more expressive.Because I'm an Emacs user, I use Lisp frequently just to keep my configuration tweaked the way I like it, but if I was creating an editor like Emacs today I think it would be better to use Python, Javascript, or Lua for the underlying programmable part of the editor.
Perhaps Haskell is heading for the same kind of niche that Common Lisp occupies today, remaining important for the ideas it explored and of historical interest but never becoming more popular than it is today.
(last '(1 2 3)) ;; => (3)
The signature for last is: last list &optional n => tail
If n is not provided, it defaults to 1 so the behavior and order of parameters is sensible in this context.http://www.lispworks.com/documentation/HyperSpec/Body/f_last...
It's also worth noting that along with nth which takes the index first, there's elt which takes a sequence as the first parameter and index as the second. aref is similar but restricted to arrays and permits multiple numbers for the subscripts since arrays can be multidimensional. char, which accesses characters in a string, takes the string first and index second as well. bit takes the bit array as the first parameter and the subscripts follow it.
The problem I have with it is the meta-language. I tried Haskell a few weeks ago and "stack" installed over 27000 files. I feel like it's a case of "Physician heal thyself." Turn the power of Haskell back onto it's own tooling. (Python suffers from a similar problem, where distributing and installing Python code has been paradoxically un-Pythonic.) Some languages like Elm and Gleam distribute single binaries that you just put in your PATH.
Given how good Haskell is at writing compilers and code analysis tools it should have a best-in-class IDE, installation process, etc. when that is most definitely not the case.
Luckily the past few years seems to have witnessed rapid improvement on this front so hopefully my gripe will be irrelevant in another few years.
Found the video - https://www.youtube.com/watch?v=CffrvwIW0JY
I'm currently a web developer, but the blockchain scene is quite interesting. Especially these new smart contracts look quite easy to play around with.
So is COBOL. Doesn't mean anything, really. Bankers' choices in tech probably has nothing to do with ideological/purity issues in software development.
I guess safe and fast fp appeals to distributed finance.
I've since gone on to do most of my career in Python and haven't looked back. But something deep inside of me certainly misses Haskell. And try as I may to let old bygones be bygones, I keep trying to figure out a good use for it. I certainly wonder whether there's a niche for convex optimization problems where I'd otherwise use `cvxopt` in Python, or something in Julia, or even (if I was feeling particularly exotic), something in Prolog.
Basically, what I'm trying to get at is whether there's something that Haskell's enormously powerful compiler could do so much better than other languages that it would be reason enough to write service code in it. Anyone more experienced in HS have thoughts on this? I know Facebook uses it in production.
FP is confusing at first but really a strong source of fun.
Here's my problem with Haskell: I want to model my domain as collections of heterogeneous maps. I work in the B2B world where our databases go back years but every week either the sales dept or our customers are throwing totally new requirements at us changing the way we think about our domain. We also do a ton of work with SQL which is trivial with such a mindset as relations are just sets of maps, but working with relations in Haskell, or any strongly typed language requires mapping layers that become their own source of unnecessary complexity.
I love the idea of the compiler doing more work for me and really do miss the advantages of type systems for communicating intent to other developers. I've had positive experiences with C# in game dev and Scala in scientific computing, but in my current role shoving the real world into a typed box has been an exercise in futility. Am I missing something about Haskell that supports this world or is it just a bad fit for my org?
But Haskell’s type safety extends beyond your immediate domain. Writing a web server, interfacing to a database, etc. all have well formulated domains and Haskell’s type safety provides a lot of value there.
Finally, I’m not denying that some domains are too chaotic and fast changing to be modeled statically. But there’s like some subset of your domain that is indeed static. Can you represent that statically and represent the other subset dynamically?
You're right the hybrid approach seems better, but there it seems more like that Python/Ruby with their new gradual typing seems like the way to go, adding strong on top of dynamic rather than trying to poke a hole in strong typing to let you be dynamic.
But it's always interesting to listen to the experience of the Haskeller's and I keep trying to find the motivation to dig deeper into it.
The responses I've received have me thinking I need to give up finding a way to directly map Haskell into my day to day work and more just approach it on its own terms and then take from it what I can.
Map ByteString ByteString
This can represent any heterogeneous map that any other language can represent. Sum types can allow us to add another layer of structure to this if we want. For example, take the classic example of JSON.
Map ByteString JValue
...where
data JValue
= JObject (Map Text JValue)
| JArray Array
| JString Text
| JNumber Scientific
| JBool Bool
| JNull
This is just one example of a way you could structure things. Sum types are super powerful for this kind of thing. And no matter what structure you come up with, you can always escape hatch that structure by adding a catch-all constructor like this: data MyValue
= MyThingA A
| MyThingB B
| ... more things
| Unexpected ByteString
That's actual valid code btw, and it even reads really nicely!What I really find annoying is when I have to update state. I always have to write tones of functions in order to re-synthesize the data after the smallest change, and the deeper the data model tree is the more functions are needed.
Did I ever have any benefit from using the IO monad or the state monad? I don't know..it seems to be the number of bugs between my non-IO code and my IO code is roughly the same.
Perhaps it shines on a collaborative level...I don't know, I never had the chance of writing Haskell code with other people.
Yes, and purity is also for your collaborators (including your future self) who didn't write the code so don't understand it as well as you do.
I hope there's one day we can answer 'Why Use Haskell?', and enjoy all the elegance and preciseness it has to offer. In reality, however, Haskell is the language that continuously and intentionally 'avoiding success'.
Scala was the 'Haskell' in the engineering world a decade ago and still is. There was a trend of hosted languages like Scala and Clojure. It was a way to attract the audience because it was the de-facto platform. Nowadays this trend started to die out, and new generations are more on bare metal, like Rust and Go.
It seems like there's a niche that looks like the new Scala while on bare metal, targeting engineers while without all the historical syntax.
Another cool aspect of Prolog is how simple it is to write a meta interpreter. Fort example, you can easily change Prolog's default depth first search through the solution space to breadth first, or iterative deepening: https://www.metalevel.at/acomip/
Whether it's prolog, Kanren, datalog or any other relational language I wish everybody to enjoy the mind expanding effect.
ps: for any scheme ready, 'the reasoned schemer' is a good book about the pieces for relprog
(describes a notation for propositional logic)
Oh. I see what you did there.