6 ms·
I think the key issue is not that successful Haskell programs are technical. I think it's that Haskell is not good for heavy-IO programs. I'm not sure how Postg
by ragnese 6y ago
I think the key issue is not that successful Haskell programs are technical. I think it's that Haskell is not good for heavy-IO programs. I'm not sure how PostgREST works, but shellcheck and pandoc, for example, are really well suited for "IO on the edges" style: you feed bytes in once, and then at the very end you spit some other bytes out. Everything in the middle is parsing and transforming data.
If you're writing some boring CRUD app, or a GUI program that makes a bunch of syscalls, Haskell sounds like a giant pain in the ass.
From that POV, I think that Haskell's relative lack of popularity is perfectly fine. Not every language has to be maximally general.
- nightski 6y agoI used to write a lot of Haskell and tbh I find the idea of restricting IO to the edges of an application to be equally as important in traditional imperative programming languages as well. It's just good design, not a PITA. It seems to be a common view point that Haskell makes IO difficult. I actually personally found that it was more powerful because I was being more explicit about it. It also allowed for higher ordered functions to abstract common patterns in IO. The only difficulty in Haskell was not due to monads or type safety, but rather in getting my brain to understand lazy evaluation by default.
- ragnese 6y agoI agree that keeping IO sequestered is generally good advice. And it's something that I also do in imperative and OO languages as a rule of thumb. But having the type system signalling at IO is happening doesn't end up being useful in non-lazy languages where purity doesn't actually help an optimizer. I found this blog post to be kind of interesting: https://blog.ploeh.dk/2017/02/02/dependency-rejection/ https://blog.ploeh.dk/2017/02/02/dependency-rejection/ Skim that and then take a look at the back-and-forth in the comments. Sometimes going out of your way to write mostly pure (or just non-IO) functions can make your code more difficult to reason about. In my experience, you're sometimes left with two choices: either do a bunch of IO upfront that you might end up throwing away (if there is an `if` statement in your pure function that determines whether you need the result of some IO op), or pull out logic into the naughty impure edge of your program. If you do the former, you're wasting performance. If you do the latter, you're not actually fixing anything that pure functions are supposed to help you with, because you still have logic in impure functions. In my experience there are these edge cases where finding the cutoff between "the edge where I'm allowed to do IO" and the "pure business logic" is not that clear or easy. Also, as the old saying goes: monads don't compose well.
- nightski 6y agoHaskell doesn't force you to make the separation either (nor does it trivialize software design). You can write all your pure code in IO if that is what you'd prefer. I look at types as more of a way to communicate to others using my code as "here be dragons, this has side effects" or vice versa. The developer using your code doesn't just have to trust a comment block, they can see it directly in the types. I agree monad composition isn't amazing. When I was writing a lot of Haskell (I was working on real time 3d reconstruction algorithms from video feeds using camera input and OpenGL output) it wasn't a very large roadblock though. But effect type systems seemed like they might be cleaner. I don't have time to read through that article right away but I saved it for future reference - thank you for sharing.
- steve-chavez 6y agoIO at the edges also gives your program the Functional core, imperative shell[1] design by default. [1]: https://www.destroyallsoftware.com/screencasts/catalog/functional-core-imperative-shell https://www.destroyallsoftware.com/screencasts/catalog/funct...
- sfvisser 6y agoMost CRUD apps and UI still has IO on the outside pure/domain stuff on the inside. Most CRUD server handlers basically boil down to fetch and parse incoming HTTP request, decide to fetch some external resources (db/cache/service), prepare a response, and reply to the client. Fits the IO-on-the-outside model perfectly. React and friends have shown that UI views are mostly pure functions. Some local state and external resource can be fetched in some runtime hook and passed into your pure renderer.
- ragnese 6y agoFor the most simple CRUD apps, there is almost zero business logic. So your whole application is basically IO. What's the point of Haskell's IO type when every function will have to be IO? For the less simple apps, IO can be hidden behind interfaces for complex business logic. Here's an imaginary example: def getPriceForShoppingCart(product: Product, user: User, getDiscount: (User) -> Percentage): Price { if (user.isPremium) { product.price * getDiscount(user) } else { product.price } } How do we put the IO on the edges? Does this function just disappear and we have an `if` statement in our impure outer shell? That smells like business logic in the impure shell to me. Do we go ahead and call `getDiscount` in the outer shell and pass in the discount to the function? That also sucks because what if we made that trip to the database when we didn't need to (because the user is not a premium user)? I feel like every time I try to be religious about IO at the edges and not using impure dependency injection tricks, I end up running into a bunch of cases like this. On the other hand, if I stand back and look at a function like this objectively... It's easy to understand, it's still quite easily testable. What's the problem?
- marcosdumay 6y agoThis function is receiving a product and a user as arguments. Why would it do any IO? Anyway, CRUD does really not lead to clear "is IO"/"isn't IO" boundaries, and the business logic isn't a good candidate for separating from IO into a pure core. Instead, it is a great candidate for separating into a "with business data" context that is neither simple IO nor pure. On the implementation of that "with business data" context you will find plenty of opportunities to push IO to the boundaries of complex code that will become pure. But your intuition is correct, what you are missing is that the code can be broken on more dimensions than the ones you are assuming.
- fegu 6y agoThe Integrated Haskell Platform (IHP) open sourced at GitHub.com/digitallyinduced/ihp is one example of how to do crud in Haskell very efficiently.