It heavily depends on the application, right? Haskell is life for algorithmically generating or analysing data, but I’m not really convinced by the ways available in it to do interaction with users or outside systems. It pretty much feels like you’re doing imperative code again just in the form of monads, after a while. Which is actually worse from a locality of reference behavior perspective.
Not really, it’s just good practice. You write your application in layers, and the outer layer/boundary is where you want your side effects and that outer layer takes the crazy effectful world and turns it sane with nice data types and type classes and whatnot and then your inner layers operate on that. Data goes down the layers then back up, at least in my experience with functional projects in OCaml, F#, Clojure, and Haskell.
The real sauce is immutability by default/hard-to-do mutation. I love refs in OCaml and Clojure, so much better than mutation. Most of the benefits of FP are that and algebraic data types, in that order imo.
I’m not sure what you mean by “locality of reference”. I assume you mean something other than the traditional meaning regarding how processors access memory?
Anyway, it’s often been said (half-jokingly) that Haskell is a nicer imperative language than imperative languages. Haskell gives you control over what executing an “imperative” program actually means in a way that imperative languages don’t.
To give a concrete example: we have a custom monad type at work that I’m simply going to call Transaction (it has a different name in reality). What it does is allow you to execute database calls inside of the same transaction (and can be arbitrarily composed with other code blocks of type Transaction while still being guaranteed to be inside of the same transaction), and any other side effects you write inside the Transaction code block are actually collected and deferred until after the transaction successfully commits, and are otherwise discarded. Very useful, and not something that’s very easy to implement in imperative languages. In Haskell, it’s maybe a dozen lines of code and a few small helper functions.
It also has a type system that is far, far more powerful than what mainstream imperative programming languages are capable of. For example, our API specifications are described entirely using types (using the servant library), which allows us to do things like statically generate API docs, type-check our API implementation against the specification (so our API handlers are statically guaranteed to return the response types they say they do), automatically generate type-safe API clients, and more.
We have about half a million lines of Haskell in production serving as a web API backend powering our entire platform, including a mobile app, web app, and integrations with many third parties. It’s served us very well.
I’m not sure what you mean by “locality of reference”. I assume you mean something other than the traditional meaning regarding how processors access memory?
Shit! Sorry, got my wires crossed, I actually meant locality of behavior. Basically, if you’re passing a monad around a bunch without sugar you can’t easily tell what’s in it after a while. Or at least I assume so, I’ve never written anything big in Haskell, just tons of little things.
To give a concrete example:
Yeah, that makes tons of sense. It sounds like Transaction is doing what a string might in another language, but just way more elegantly, which fits into the data generation kind of application. I have no idea how you’d code a game or embedded real-time system in a non-ugly way, though.
It also has a type system that is far, far more powerful than what mainstream imperative programming languages are capable of.
Absolutely. Usually the type system is just kind of what the person who wrote the language came up with. The Haskell system by contrast feels maximally precise and clear; it’s probably getting close to the best way to do it.
Excellent write-up. People who complain about Haskell and purely functional languages just don’t understand it, I think. Take me for example. I tried learning Haskell many years ago, and while I learned so many new and incredibly useful concepts from my short adventure, that I use everyday in my career, I just couldn’t wrap my head around the more abstract concepts, like monads e.g. And the feeling I got was that Haskell is a difficult language, but probably it’s the terminology and abstract mathematical concepts which are the real issue for me here. Because the syntax isn’t really that complicated. Especially the way space is used to call functions. I’m really sick of all the parentheses in other languages.
But, if you understand all about functional programming, for those that do, it seems to really enrich the way they write and maintain code from what I’ve seen. People who dog on it just don’t understand (including me). Of course it’s hard to maintain something you don’t understand. But if you do understand it, it’s easy to maintain. 🤷♂️ Seems logical.
What next, where is the line drawn for what kind of code we can write? Why introduce more useful concepts in programming if we risk losing maintainability because some devs won’t learn the new concepts?
Life means change. Adapt. Learn new things. Expand the mind. Learn how to do things in a good way, and then do the things in that good way. Why stagnate just because we don’t understand something. Better to learn a new thing to understand the better way, than to dumb it down to a worse state just so we understand it.
It heavily depends on the application, right? Haskell is life for algorithmically generating or analysing data, but I’m not really convinced by the ways available in it to do interaction with users or outside systems. It pretty much feels like you’re doing imperative code again just in the form of monads, after a while. Which is actually worse from a locality of
referencebehavior perspective.Not really, it’s just good practice. You write your application in layers, and the outer layer/boundary is where you want your side effects and that outer layer takes the crazy effectful world and turns it sane with nice data types and type classes and whatnot and then your inner layers operate on that. Data goes down the layers then back up, at least in my experience with functional projects in OCaml, F#, Clojure, and Haskell.
The real sauce is immutability by default/hard-to-do mutation. I love refs in OCaml and Clojure, so much better than mutation. Most of the benefits of FP are that and algebraic data types, in that order imo.
I’m not sure what you mean by “locality of reference”. I assume you mean something other than the traditional meaning regarding how processors access memory?
Anyway, it’s often been said (half-jokingly) that Haskell is a nicer imperative language than imperative languages. Haskell gives you control over what executing an “imperative” program actually means in a way that imperative languages don’t.
To give a concrete example: we have a custom monad type at work that I’m simply going to call
Transaction
(it has a different name in reality). What it does is allow you to execute database calls inside of the same transaction (and can be arbitrarily composed with other code blocks of typeTransaction
while still being guaranteed to be inside of the same transaction), and any other side effects you write inside theTransaction
code block are actually collected and deferred until after the transaction successfully commits, and are otherwise discarded. Very useful, and not something that’s very easy to implement in imperative languages. In Haskell, it’s maybe a dozen lines of code and a few small helper functions.It also has a type system that is far, far more powerful than what mainstream imperative programming languages are capable of. For example, our API specifications are described entirely using types (using the servant library), which allows us to do things like statically generate API docs, type-check our API implementation against the specification (so our API handlers are statically guaranteed to return the response types they say they do), automatically generate type-safe API clients, and more.
We have about half a million lines of Haskell in production serving as a web API backend powering our entire platform, including a mobile app, web app, and integrations with many third parties. It’s served us very well.
Shit! Sorry, got my wires crossed, I actually meant locality of behavior. Basically, if you’re passing a monad around a bunch without sugar you can’t easily tell what’s in it after a while. Or at least I assume so, I’ve never written anything big in Haskell, just tons of little things.
Yeah, that makes tons of sense. It sounds like
Transaction
is doing what a string might in another language, but just way more elegantly, which fits into the data generation kind of application. I have no idea how you’d code a game or embedded real-time system in a non-ugly way, though.Absolutely. Usually the type system is just kind of what the person who wrote the language came up with. The Haskell system by contrast feels maximally precise and clear; it’s probably getting close to the best way to do it.
Excellent write-up. People who complain about Haskell and purely functional languages just don’t understand it, I think. Take me for example. I tried learning Haskell many years ago, and while I learned so many new and incredibly useful concepts from my short adventure, that I use everyday in my career, I just couldn’t wrap my head around the more abstract concepts, like monads e.g. And the feeling I got was that Haskell is a difficult language, but probably it’s the terminology and abstract mathematical concepts which are the real issue for me here. Because the syntax isn’t really that complicated. Especially the way space is used to call functions. I’m really sick of all the parentheses in other languages.
But, if you understand all about functional programming, for those that do, it seems to really enrich the way they write and maintain code from what I’ve seen. People who dog on it just don’t understand (including me). Of course it’s hard to maintain something you don’t understand. But if you do understand it, it’s easy to maintain. 🤷♂️ Seems logical.
What next, where is the line drawn for what kind of code we can write? Why introduce more useful concepts in programming if we risk losing maintainability because some devs won’t learn the new concepts?
Life means change. Adapt. Learn new things. Expand the mind. Learn how to do things in a good way, and then do the things in that good way. Why stagnate just because we don’t understand something. Better to learn a new thing to understand the better way, than to dumb it down to a worse state just so we understand it.
Bah.