Recorded 2025-03-31. Published 2025-08-12.
In this episode, we’re joined by Michael Snoyman, author of Yesod, Conduit, Stackage and many other popular Haskell libraries. We discuss newcomer friendliness, being a Rustacean vs a Haskellasaur, how STM is Haskell’s best feature and how laziness can be a vice.
Matthías Páll Gissurarson (0:00:15): Hi, and welcome to this episode of the Haskell interview. My name is Matti.
Samantha Frohlich (0:00:21): And I’m Sam.
MPG (0:00:22): Today we’re joined by Michael Snoyman, author of Yesod, Conduit, Stackage, and many other popular Haskell libraries. We discuss newcomer friendliness being a Rustacean versus a Haskellasaur, how STM is Haskell’s best feature, and how laziness can be a vice. Welcome, Michael.
Michael Snoyman (0:00:41): Hi. It’s great to be here.
MPG (0:00:43): So the question we start with usually is, how did you get into Haskell?
MS (0:00:48): Well, that’s going into ancient history, I guess, at this point. The way I got into Haskell was, I think, I’m not unique in this one. I got in via Perl 6. When I was in college, I worked at a computer lab on campus and had one of those gray beards, and he convinced me that I got to learn Perl. Perl is the greatest programming language that’s ever existed, and I think I did a solid two or three years with Perl 5 as my primary language. That was around the time that Perl 6 was starting to come out, and the first implementation was this thing called Pugs, and everyone got really interested. A bunch of people got really interested. It was interesting in this. It was written in this weird language called Haskell. No one really uses it. It’s a strange academic thing. And yet I opened it up. I looked at it, and I’m like, “Holy crap. This is everything that I could possibly want in language.” And again, I’m pretty sure there are a few other people who have an almost identical story.
At that point, there wasn’t really a lot of educational material, at least at my level, to be able to get into it. So I think a lot of us cut our teeth on a bunch of the topics in Haskell. At the time, I definitely did. Took me a long time to get past monad transformers in particular. That’s usually the story that I tell people, and it’s true. I took a six-month hiatus once I heard the term “monad transformer,” but eventually came back to the language, loved the language, and kept going with it.
MPG (0:02:11): Right. And then, yeah, because you contributed a bunch of packages to the ecosystem, you know? And then I think the first time I heard of you was when I used Yesod. Did that come out of a project you were working on, or was it just something you wanted to play with?
MS (0:02:30): The genesis of that actually is very straightforward. I had been working – I’d lived in the – you can probably hear it in the accent. I’m born and raised in the US, but I did move to Israel, uncoincidentally, at the same time that Yesod is coming into existence. I was looking for a new career, looking for a new direction. I hadn’t worked in computers up until then. Not directly. I had been a programmer, but a programmer in the insurance industry. I’ve been an actuary at Farmers Insurance. So that didn’t give me a lot of room to play with a lot of good programming languages. In fact, the one time that I used C++ to write something, which is not a good language—the one time I used C++ to write something that they told me, “Okay, that’s great. Now go rewrite it in VBA, like a good boy.” So I was definitely looking for good languages to use.
When I moved to Israel, I knew I wasn’t going to be getting back into the insurance industry. So at that point, I decided, you know what? I did some web development when I was a teenager. I’ve kind of used this Django thing. I really liked Django. I thought Django was a great web framework in Python, but I wasn’t a fan of Python. Python is a good enough language, but it doesn’t have a type system. And so basically, the idea behind almost everything I did with Yesod was, can I take what’s already out there in the Ruby and the Python spaces, which were the big boys at the time. Can I take what’s already out there and bring that over to Haskell and get all the benefits of Haskell without reinventing the wheel on how to do things in web development?
SF (0:03:53): What did you use it for initially?
MS (0:03:55): Initially, I used it for my own edification, just for having fun, learning things, building up some hobby sites. I did end up doing some consulting with it. That was the first job that I ever got in Israel, was building out some sites with Yesod. And then I went on from there, worked at another company and event, and not too long after that, I ended up joining up with FP Complete pretty close to when FP Complete got started. The mission of FP Complete was very simple, straightforward. We have a crisis in the world. There’s no good software development language that people are using. It’s actually very difficult, I think, for a lot of people today with the way that languages are set up today to understand how bad things really were 15 years ago. So at that time, it made a lot of sense, and I still think Haskell is one of the top languages out there for doing a lot of these things, concurrency in particular. So Haskell is really solving a lot of problems that people were suffering on a regular basis. So the idea of “let’s build a company that’s built around the idea of getting better software tools into people’s hands” lined up perfectly with what it is that I wanted to be doing.
MPG (0:04:59): Right. Yeah, because FP Complete also did a lot of work. I remember this was 15 years ago. So about the same time as I was getting into Haskell, right? And then there was a lot of tools just missing at that point, I remember. But I think you guys did in Taro also, like the IDE helper, right?
MS (0:05:22): So what we did, we did build the web-based IDE. That was our initial product. It did not actually succeed very well. That ended up morphing into School of Haskell and providing a web-based experience. That’s yet another one of those examples where now everyone in the world understands, yeah, you can write code in your browser. Back then, that was not a very well-understood thing, and there were a lot of tooling issues that people were running into. It was built around the idea that a lot of people were hitting these barriers to adoption early on in the process, and so we tried to knock those down. Later on, we took different approaches to that. Things like the Windows installer and things like that—those all played into the idea of really making it as easy as possible for people to adopt the language.
SF (0:06:02): And which of those ways did you find was the most successful to lure people in?
MS (0:06:07): The web-based IDE by far was not the most successful way to do things. Haskellers like control. Haskellers like to be able to run things themselves on their own local machines. Maybe if we’d been doing that for a different programming language, it would’ve had a different outcome. I’m not even convinced that’s true at this point. Developers overall like to control things. Haskellers are just a little bit stronger in that tendency than most. I think that, honestly, the Windows installer, I know that’s morphed into many other things. It got merged into Stack at some point. Now we have GHCUp. We have a lot of different approaches, but simply, the fact that we – I’d like to believe we set a new bar. At that point, it was Windows users need an easy way to install things. And you know what? Even Linux and Mac users need an easy way to install things. We don’t need to suffer in order to use this language. We can just have a language and have tools that are easy to get access to. So I think that’s now become – I’m not sure if it’s purely us. As I’ve mentioned a few times, there are other languages that have popped up with great tooling and a very easy user adoption path. I think that’s really become the default at this point, and it is really good to see that Haskell is adopting that. And even though we don’t maintain a Windows installer anymore, Windows installers exist, even though Stack has some of these things, GHCUp is out there, and many other tools. So there are multiple ways to get the tooling for Haskell without needing to have a PhD.
MPG (0:07:27): Right. Yeah. I think it was mentioned at some Haskell Implementors’ workshop that I think Simon Peyton was the only Windows user of Haskell. So if it didn’t work for him, that’s when we found out.
MS (0:07:39): I actually punished myself for about six years. I used a Windows machine exclusively for about six years, just to make sure that the Haskell tooling always worked correctly on a Windows machine. I’m very happy to be back on a Linux machine again, and I never intend to go back into Windows, but it’s a good thing that I got to feel those paper cuts for quite a while.
MPG (0:07:56): Yeah. I mean, because you did mention Stack. We’re not going to get too much into the whole fight, but can you just tell us, what was the inspiration for that originally?
MS (0:08:07): Yeah. You’re probably going to hear a pattern at this point, and I’ll give some philosophy around this. The philosophy is pretty simple. There are attractors, and there are obstacles. This is something I love from Aaron, the founder of FP Complete. Haskell has a lot of attractors. Concurrency is the one that I always point to, like STM. No language out there has anything close to STM as far as I’m concerned. There are lots of things that approach it, but it’s not great. I’m working on a project right now in Rust that, if I had STM, the project would be 10 times easier. But that’s simply not available.
So there’s all these great attractors in Haskell, the type system, everything else, but there are obstacles, and the obstacles get in the way. I personally went through a lot of experiences trying to onboard people, either personally or through the company, and we heard the same messages coming back over and over again. I had many people who wanted to use Yesod, and they would say things like, “Well, I ran cabal install. It took an hour, and it didn’t find a build plan, so I gave up on Haskell.” That’s not a good experience.
The genesis of Stackage and then Stack was purely around, can we knock down this obstacle? Is there any kind of possibility that we can build a build tool which is best in class, or at least close to best in class, does the right thing, people don’t need to think about this, they can clone a project to type in stack build, get their toolchain, get built, and get running.
MPG (0:09:28): Right. I think it influenced a lot of the modern design of Cabal, right? Just the way the stack kind of started doing things like reproducible builds. And I think now, even, I think we have Stackage support in Cabal, right, where you can just specify that you want that as the resolver. So, yeah. I also remember that, yeah, like 15 years ago. I was using Xmonad, and then sometimes my window manager would not build anymore, and it was just a very painful kind of this cabal hell, as it was called, right?
MS (0:10:07): I won’t name the other programming language because I’m not trying to beat up on other programming languages, but I did hear that there’s another language out there that has a term called “uptime.” And uptime does not mean how often the server is still running; it means how often the builds succeed, and they targeted 95% build time. If it builds 95% of the time, we’re okay.
MPG (0:10:23): Right. I’m also curious because you were very involved in the Haskell community, but then you moved on to Rust. And I know it’s a Haskell podcast, right? But can you tell us a bit, like, why did you move away, and tell us about how did a Haskeller become a Rustacean, I think it’s called?
MS (0:10:47): Why don’t I start on the why, and then I’ll talk on the – because that’s the boring part. The actual interesting part is what happened technically. The why is more of a business reason. At the company and me personally, we were actually seeing a decline in the number of people who wanted to be getting Haskell services. That’s, for the most part, continued over the past few years. It wasn’t a very difficult decision for us to make to say that we’re going to start branching out some more. And then eventually at this point, Rust really is the primary language of development at FP Complete and for me personally. Those were not easy decisions to make. They were made with many tears along the way. Let’s put it that way. But what it came down to is the industry really did make a decision. Why did Rust win? That would be a very interesting discussion. I think it’s very valuable to be able to talk about that, but since this is a Haskell podcast, I’ll give you the best Haskell answer.
Rust won because it stole every single good idea it possibly could from Haskell. And that’s part of the reason why I think a lot of Haskellers feel more comfortable in Rust. If you had said to me 10 years ago – let’s say 10 years ago. If you had said to me 10 years ago, “You know what, you’re going to be happy to not write Haskell on a day-to-day basis, and instead, you’re going to be using a systems programming language with no garbage collector and low-level memory,” I would say, “You’re crazy.” Rust brought in this language that gave us ADTs, gave us manual memory management without all the pain, with a really powerful type system, all traits. So it really did give us the benefits that we were looking for.
I happened to have been at a conference last week in Poland, Rustikon, and sitting around the speaker’s table. It was very interesting. I had almost no overlap with most of the people there. They were all talking about these advanced performance optimizations, and they sound cool. I think, like any engineer, I love hearing about these stories. Reality is those are not the things that drew me into the language or what I spend most of my time dealing with. Most of my time is spent on making sure that whatever system we’re building is built correctly and isn’t going to steal somebody’s money or lose somebody’s money or kill somebody if it’s a medical app or whatever it is that we’re dealing with. Rust is giving me a lot of the same benefits. And so as far as that transition into Rust, the fact that I’m able to take all of my Haskell instincts, maybe not all of them, but 80, 90% of my Haskell instincts, just translate naturally over to Rust, that’s really nice. The fact that immutable by default, just picking that as an example, that’s obviously the way Haskell works. Rust has picked up on that. And the really amazing thing is almost every programming language today, they tout that as a feature, or at least a goal that you should strive towards. If you’re sitting there and writing TypeScript code and you’re doing everything in a mutable way, someone’s going to yell at you at some point.
So one of the things I think that we’ve really succeeded with as a – I’m going to take some credit to the Haskell community overall, even though there are more languages in this community, we as FP people have really pushed some great ideas into the rest of the space, and I think the entire programming industry is better off as a result.
MPG (0:13:41): But what do you most miss about Haskell? Do you want lazy evaluation in Rust, or is that kind of, no, we don’t need that?
MS (0:13:50): Actually, we could go through a list of all the different features, but if you’re going to say what do I miss the most, I already said it. It’s STM. Lazy evaluation I don’t find that I’m missing very often. Rust has done an interesting job by providing essentially lazy and eager versions of a huge number of standard library functions. That feels very dirty as a Haskeller. Why do you need two versions of the functions? Rust overall has the two-color problem, if we’re going to talk about that in general. In fact, in Rust, you can say that there’s many colors. It’s lazy versus non-lazy. It’s async versus not async. It’s failing versus result, option, and pure. All of these are different forms that a lot of the functions are able to take, and it seems like, as a Haskeller, that’s wrong. We should generalize this. We should have an abstraction. We should be able to talk about these things as monads and have the identity monad and all the other things that we should be able to do.
Rust took a very different kind of approach. I was very resistant to that approach at first. It felt wrong. It felt like it was cheating. At the end of the day, I think I’ve kind of either come to terms with it or now I fully embrace it. I’m not quite sure which one it is at the moment. Maybe it’s a little bit of Stockholm syndrome also that I’m stuck dealing with it, so I may as well enjoy it. But overall, I think the outcome, I think it’s an interesting thought experiment for a lot of Haskellers. Rust decided we’re going to be a little bit more opinionated. Not as opinionated as other languages like Go. Go is a far more opinionated language than Rust is. But Rust is definitely more opinionated than Haskell. It’s going to tell you the way to write code. The easiest example of that, the formatter is built into the standard toolchain, and almost everyone uses the formatter out of the box. That’s a minor example in my opinion. I don’t think the syntax is the most important part of any kind of project, but you can see the mentality going in right there from the beginning.
SF (0:15:38): Nice. So you’ve said a lot about how Rust learned from Haskell and how you liked that. Is there anything you think Haskell could learn from Rust? Because I know that Rust is quite popular, and all the developers really do love it. They call themselves Rustaceans, was it?
MS (0:15:53): Right.
SF (0:15:53): But if we were to flip it and you could go to Haskell and add something from Rust, what would you add?
MS (0:15:59): Well, I’m not sure if I would add anything to Haskell. I’d probably start taking things away from Haskell.
SF (0:16:04): Or that.
MS (0:16:04): I think one of the biggest problems that we have is that there are simply too many different ways of doing things. Getting away from too many comparisons to Rust. One of the other very interesting comparisons that pops up is Elm. So Elm, maybe it’s not even a really different language than Haskell. Maybe it’s a dialect of Haskell. But if it’s a dialect of Haskell, it’s a very limited dialect of Haskell. Every single Haskeller that I’ve ever spoken to dislikes Elm out of the box. They may be willing to work with it, but they don’t like the fact that they don’t have monads and type classes, and their hands are tied. And every non-Haskeller I ever speak to who’s used Elm loves Elm because they didn’t have to think about all these other things. It’s an interesting kind of dichotomy. Is this a selection bias of the people who would choose to use Haskell automatically feel this way? Is it maybe that Stockholm syndrome comment that I made before? Are both sides having a little bit of it? I’m not quite sure, but I think there’s something to be learned from this idea that having less functionality gives you more freedom.
MPG (0:16:58): So it should be more opinionated in Haskell?
MS (0:17:00): I think so. I mean, also part of the problem of being opinionated is which opinion do you take? There’s that old XKCD, you have 14 standards, and now we’re going to make the 15th standard.
MPG (0:17:09): Right.
MS (0:17:10): That also applies here. There’s, how many different ways are there in Haskell to do pattern matching in a function? In function parameters? There are quite a few. Do I want to get rid of any of them? I look at this, and I say it’s not really worth getting rid of any of them. They’re all very simple. They’re all straightforward. I don’t see the problem with the fact that I can think of at least three ways off the top of my head to do pattern matching on a function argument. On the other hand, Elm decided, no, we’re not going to go in that direction. Rust decided we’re not going to go in that direction. Most languages have actually gone in a different direction. So maybe they have something there. I’m not sure.
As much as I’ve been with Haskell for a very long time, language design was never one of the things that I was ever really interested in. For the most part, it was, “Let’s use the language the way it is right now.” But after enough experience, I am seeing, yeah, maybe there were some things that could be changed. I wouldn’t consider myself an expert to figure out what the right set of changes would be, but I obviously have my own opinions on it.
SF (0:18:08): It’s interesting as well that you bring up Elm because that’s a language that’s found a lot of success in having a browser version. Whereas earlier, you found that people didn’t really like the browser Haskell. They preferred to have control. Do you think that’s because things have changed, or do you think that’s because Elm is different?
MS (0:18:25): So I’m not actually that familiar with doing web dev. Sorry. You mean doing the development itself in the browser or running –
SF (0:18:32): Well, they just have a lot of nice tutorials and a lot of accessibility in the browser.
MS (0:18:38): So I haven’t actually used Elm myself very much, so I don’t know exactly the material. Definitely, one of the things I’ve heard from Elm is that there is, as you’re describing, a very nice set of user tutorials. User onboarding is very pleasant there. That could be part of it, and it could be that they’re really targeting a different audience that wants that kind of thing. Haskellers, we make the jokes all the time that go, “Read my paper if you want to understand what’s going on.” But there’s actually a certain subculture within Haskell that’s saying, “Yeah, but I want to read the paper. I don’t want to go read some nice user-friendly tutorial. I want to read the real thing and find out what’s actually going on there.” So it could be that it’s speaking to different audiences, and we naturally ended up with an Elm community of people who care a lot about shipping code that works without much fuss. And a Haskell community, that’s much more about, I want to understand the intricacies and create beautiful abstractions and create things that are going to be more powerful than anything else in any other language.
MPG (0:19:35): Yeah. Because you mentioned the user tutorials for Elm, and I’m wondering because – and then you mentioned the School of Haskell before. Was that driven by user adoption for FP Complete, or how did that come about?
MS (0:19:50): So it definitely didn’t drive any user adoption for FP Complete. We went through a period where the goal was really to get Haskell to be more used overall, the rising tide of solid boats kind of idea. So we wanted to just go ahead and make a platform. We already had the technology in place. We’d already built the web-based IDE. So all of the heavy lifting, all of the DevOps behind the scenes in order to provide secure execution environments, all that had already been done. So lifting that up to do a School of Haskell was a natural transition. And as I mentioned, it was not a commercial success building a web-based IDE, so we may as well get something out of it. Plenty of people were able to interact in ways that they hadn’t been able to previously. And maybe this does really tie in, Sam, to what you were saying. The idea that someone was able to come to School of Haskell, press a button, see something run, and then make some changes. Again, today, lots of websites provide lots of capabilities for doing that, but 10 years ago, that wasn’t the case. So I’d like to think that we had some kind of an impact with that. A positive. I’d like to think we had a positive impact with that.
MPG (0:20:54): Right. So, yeah. Because now you’ve moved on, but I’m wondering, like, if you start with Haskell again, is there anything you would’ve done differently knowing now what you know or knowing then what you know now?
MS (0:21:05): If I could go back in time 15 years, I’d avoid all the mistakes I made. If I was to start Haskell today versus 15 years ago, it would be a totally different story. The tooling, the materials available, the libraries available, I would not end up doing the same thing. I wouldn’t go off and rebuild a bunch of libraries. The only reason I built the libraries that I did was because there was a gap, and I was trying to fill the gap, at least for myself. If I could go back in time, I’m sure that there would definitely be some changes that I would make along the way. But for the most part, I think most of the things that I ended up doing, those were necessary things in order to figure out how exactly does this Haskell language work. I would love to be able to not make terrible API mistakes and decisions that I made along the way, but those were the growing process. So I assume I would end up making the same ones again.
MPG (0:21:53): Right. Yeah. So we talked a bit about Yesod, but you had a lot of other libraries, right? Can you tell us a bit about, like, how did they come about, and which is your favorite library, basically?
SF (0:22:05): Picking your favorite child.
MPG (0:22:07): Yeah.
MS (0:22:08): No, no, no. Probably the library that I enjoy the most that I wrote would probably be Conduit. The core principle is much smaller than any of the other libraries that I wrote. It’s more theoretical than most of the other things that I did. It still serves a real-world function. I got to iterate on the API design quite a bit. Got a lot of community feedback that really drove the design. It was one of my favorite libraries to get to write. A lot of the other libraries that I’ve done mostly came down to I having an immediate need, and I’m going to go solve that immediate need. Those would be things. So Yesod itself spawned probably a dozen Yesod-named libraries. So there’s yesod-auth, yesod-newsfeed, yesod-core, a bunch of other things. That’s also another fun part of this design space. When do you break it up into multiple packages when you have this big monolithic package? I think a lot of past colors have had to face these kinds of decisions before. But there’s a kid’s book, If You Give a Moose a Muffin, or If You Give a Mouse a Cookie.
So once I started writing Yesod, and now I have a web framework, well, obviously, it’s not enough to just serve over HTTP. Now I also have to query over HTTP. It’s not enough to just serve over FastCGI connected to Apache, which is actually the way I deployed things originally, if anyone can remember all that long ago. Instead, we needed a web server, so we were at Warp. And going on from there, that’s really the genesis of almost all of the libraries that I wrote. Usually, it was either directly for building Yesod and Yesod-powered websites, or later on, as I got deeper into the FP Complete space and we started building things out for a large number of customers, we did end up building out quite a few other libraries. That weren’t directly web framework related, but that probably ended up getting pulled in there too.
MPG (0:23:51): Right. Now, because you mentioned Warp, and I think I keep bringing that up in internet discussions when people are like, “Oh, Haskell is not performant.” And then, because Warp can do an amazing amount of requests faster than Nginx, even, right? So is that just due to Haskell, or how did that come about?
MS (0:24:14): That’s due to Kazu. Kazu did a huge number of optimizations. I wrote the basics. I got something that basically worked in place, and it was good enough. And as I mentioned earlier, I’m not a huge performance person in the first place. I mean, I will tinker when I can, and I enjoy it, but that’s not where my heart and soul is. He took that library, and he turned it from a kind of clunky web server that kind of sort of worked to a beast. And then it became not just a beast for performance, then it became a testing ground for HTTP/2 and now HTTP/3 as well, as far as I understand. And I believe, as far as I – if I try to be as objective about this as possible, did Haskell make it better or worse for being able to write the performance-sensitive code? Haskell is a little bit of a mixed bag on this. There are pain points that you get out of Haskell that you need to work around in order to make your code faster. However, for the most part, the fact that you get such correctness out of the box means you’re at least starting from a correct starting point, and then adding the optimizations can be better.
I think this is a highly debated topic, especially since we’ve discussed Haskell versus Rust. You can definitely see there’s a comparison between those two languages as well. Rust would probably be more of the ilk to say we’re going to start off with the most high-performance version possible. But that’s not even true necessarily for a lot of Rust developers. When I’m writing my Rust code, I am of the variety who says, “You know what? I’m going to write slow, crappy Rust code the first time around because I can’t figure out the lifetimes, or I can’t figure out whatever other thing I’m going to need to deal with. I’m going to clone the hell out of everything, throw a few arcs in, and then we’ll optimize it after the fact.” Rust is something that makes that very nice because it has all those safety features.
I think Haskell does an even better job in many ways. If you write correct Haskell code in a web server like Warp, as an example, you might want to use an MVar at some point to be able to do some kind of – the most practical example I can think of is date management, believe it or not. One of the things that really slows down processing of server requests is you need to look up the date and then render the date. That was something where we were able to put in some very nice optimizations. I think it’s the auto-update package that gets used for this. So auto-update was able to bypass a whole bunch of locks and do things much faster, be able to take advantage of builder APIs, which are also another wonderful thing that exists in Haskell. So you have a nice solid basis of, “I’ll just use an MVar and I’m just going to lock,” and then you’re able to transition to, “Well, what if I tweak it this way? What if I use an IORef instead? What if I use this instead?” And all of a sudden, you’ve sped your program up significantly. And Haskell is very amenable to that.
SF (0:26:56): So say we had a listener, you obviously have a lot of experience doing sort of web dev through Haskell. Let’s say they love Haskell, but they sort of always use it for more niche things, and they’re like, they want to get started and do some web dev. With all your experience, what sort of stack would you recommend? What libraries do – you know, “Go ahead, go get this, and you’re all set.” What would you recommend?
MS (0:27:19): So I have to say Yesod. I would be cheating if I didn’t say Yesod. I would actually recommend Yesod. I do think that it’s designed to handle most common use cases easily. I know that a lot of people want to have more direct control, but especially if you’re talking about someone who’s just getting started, that’s where I would point them to. Things may have changed, and some of the other frameworks – I’ll mention some other frameworks next. Some of the other frameworks may have gotten some more batteries included since then, but I think Yesod was the most on the batteries-included side, and that’s really where I see a lot of people fall down when they pick up a new language. That said, if you’re looking for something that’s going to be a little bit more lightweight, other things in the Warp ecosystem, the Y ecosystem, make a lot of sense. So Scotty and Spock were some of those that were around quite a bit before.
And I’ll say directly, I don’t recommend Servant for most people. I know a lot of people love Servant, especially though the use case that you’re talking about of someone just getting started, I think you’re more likely to scare someone off. And I think this is something we have to remember as Haskellers overall. Let’s try not to scare off the new people by recommending the single most sophisticated approach you can possibly think of the first time out of the gate. I’m not saying Servant is the most sophisticated. It might actually be, though, for web framework, web server development. And I understand it brings a huge amount of power, and people love it. I’m not saying otherwise, but for someone who’s just getting started, I think it’s going to scare them.
SF (0:28:39): Is there a particular tutorial or book you would want to share out?
MS (0:28:44): Oh, I mean, the Yesod book is still –
SF (0:28:47): I know. I’m letting you plug in.
MS (0:28:49): No, I mean, anyone who wants to buy the book, you can. It’s also available for free on yesodweb.com. Just go to yesodweb.com/book. We have actually a few prior versions of the book also up, which I can probably get rid of at this point. I don’t think anyone still wants to use a 10-year-old version of Yesod. So I can probably simplify my repo setup. But that would be the place to get started.
MPG (0:29:13): So one thing that’s been a problem in Haskell, I think, is this functional architecture in the sense that you have a big Haskell project, and then how are you going to split it up? Where are you going to put the functions? Where are you going to put the data definitions? And you’ve obviously written a lot of big packages. So could you describe how you approach the module placement problem basically?
MS (0:29:38): Interesting about the module placement. So I definitely am someone who follows the internal module approach. I think providing a stable API is important. I used to not do that quite a bit. I would change my APIs on a regular basis, and it would cause a lot of pain forever in an ecosystem, myself included. So I did grow up from that at some point, and I did adopt this internal module, internal data types. It does create quite a bit more boilerplate in some cases, but I do think the boilerplate is worth it.
Beyond that, I don’t have very many opinions on the way that modules should be set up. I think flatter hierarchies tend to work out a little bit better. I think having some kind of a top-level module that exports essentially a Prelude, a Prelude-ish for your – or in some cases an actual Prelude. ClassyPrelude.Yesod is intended to be a complete replacement Prelude. So some of those things can make sense. But even if you’re just writing a package like Conduit, we provide Data.Conduit with the low-level stuff. But there’s another package, conduit-combinators. It has a Conduit module that exports virtually everything.
I know we get into a lot of debates in the naming of functions. Essentially, do we do Hungarian notation or not? Do we include the name of the thing we’re dealing with in the name of the function, or do we not? Do we rely on qualified modules? I tend to prefer not leaning too heavily into qualified modules. I think it creates much noisier code. But I understand the reasons why people like going in that direction. It’s not my top preference. Overall, though, whatever code base you’re going to be working on, just stick to the conventions. That’s my recommendation and my plea to everyone. Please just stick. If you’re working on somebody else’s code base and they’re following a different set of conventions, that’s not the time to go fight a holy war. Write the code the way that everyone else is doing it. Follow the conventions over there. Probably everyone’s going to be a little bit happier.
MPG (0:31:27): Right. Because in Yesod, you split it into a lot of packages basically.
MS (0:31:34): All that. Yes.
MPG [31:36): When do you decide, “No, no, this is something that should be a separate package”?
MS (0:31:40): Got it. Okay. That question makes a lot of sense, and I wish I had a good answer for that one. It’s really tricky. The advantage of splitting things up into smaller packages, you can focus 100% on the API right now, design exactly the API you want, not pull in all the extra dependencies, faster build time, and now this package is going to be more generally usable by other people. On the other hand, if you build a larger package, you now have the flexibility. One, you actually end up having faster build times overall because each package has at least, the last time I checked, a decent overhead for building each package. So that’s a problem.
The code maintenance is a pain. Once you have one of those many packages in the Yesod ecosystem change of major version number, that suddenly has a cascade effect on a bunch of other packages. Now you can say the same thing is going to apply when you have one single large package, which is true. To some extent, that pain is a good thing. That pain is a nice, good reminder. Do I really need to completely bump the version number of Yesod from two dots or whatever? 2.0 to 2.1. Do I really need to do that so that I can fix the naming because I accidentally used British spelling instead of American spelling or something else? Maybe that isn’t worth causing every single person in the entire ecosystem to have to go and update their version bounds. So I think it’s worth having a little bit of friction there. So people have to think about it.
And that’s one of the downsides of a small package. With a small package, you can think, “You know what, all I’m doing is changing this one function.” And it’s not a big deal. Anyone can update it. If I did that today with – what’s a low-level dependency that I’m maintaining? Like with Y. If I just released a new version of Y tomorrow, there would be a huge amount of ecosystem churn for no reason. And we’ve all suffered from that. We call it the treadmill, the Cabal treadmill is what it used to be called, I guess. Cabal build treadmill still applies. And finding ways to avoid that, usually through keeping stable APIs, is important. If you’re already at the point, though, of stable APIs, big package versus little package, it gets to be very – it’s a debate between forcing people to take on your dependencies versus forcing yourself to maintain way more packages than you ever wanted to.
MPG (0:33:54): And I guess it simplifies a bit where to fix bugs, right? If they have an issue with a separate small package, then you can deal with that directly, and then people not impacted don’t have to update, right?
MS (0:34:08): That’s true. On the other hand, when you have a lot of very interconnected packages, you get into wonderful situations where an old version of one package and a new version of another package leads to some bug that you never would’ve caught in any kind of compatibility matrix testing. That is something also to keep in mind. There are additional downsides.
MPG (0:34:25): Right.
MS (0:34:26): So for the record, I have no good answer to anyone. You can do whatever you want, and I’m not going to judge you because I can’t figure it out myself.
SF (0:34:33): Do you have any other advice for maintaining things? You know, Stable API. Anything you’ve learned from experience?
MS (0:34:42): I’m not sure if I have any particular nuggets to give. It’s just one of those, you grind it out, you get used to it over time. If I had any wonderful words on it, I probably would’ve thought of them by now. I haven’t thought of anything in 15 years. So I wish I could share something with people. But yeah, the stable API one is a major one. And I guess just think about the use case, think about the users. You know what? I will throw one other thing. YAGNI is a term that gets thrown around, You Ain’t Gonna Need It. So as you’re building a library and – it’s one thing if you’re building an executable, you’re building your own program. You are like, “I know for a fact I’m going to need to look up the number of rows in that table, in that database. I’m going to go write that function right now.” So you spend five minutes, you write that function, and you never use it ever again, and you’re an idiot. Okay. I mean, like, what’s the harm? It’s not that bad. Sure, if you do that all the time on your project, you’re never going to deliver your project. But that’s the only downside.
With a library, once you write a function. You are pinning yourself down. If that function now requires a certain way to structure your internal data structures or something else along those lines, you now have a terrible choice at some point in the future of, well, maybe I’m going to pin myself down to this awkward data structure. Maybe I’m going to remove the functionality that users are depending on, or maybe I have to come up with some really sophisticated solution. So that is one thing to keep in mind. And this actually goes in the direction again of having smaller packages. You can have a small package with the core ideas that you’re completely certain of and then have separate packages with some of the more experimental ideas to see if they catch on.
And so I guess now you guys did get me to actually give a straight answer on all this. Each package, make sure that you understand the stability goals that you have in mind for it, hit those stability goals, and other things that don’t fit in with those stability goals or are radically different pieces of functionality, those should probably go into a different package.
MPG (0:36:45): So when you evaluate contributions to your packages, you are very strict on keeping these things clear, I guess.
MS (0:36:57): Yeah. Anyone who’s sent a pull request will probably hear me say a few things, such as, did you update the change log? Did you add a since comment? Did you bump the version number in the cabal file? And you will almost certainly have gotten some pushback if you tried to do any kind of a breaking change. Do we really need this breaking change? So those are usually the four things on my checklist. And then the normal questions, like, does this code work? Is this useful? Those things, of course, need to be there. I’m just talking about my specific idiosyncrasies.
MPG (0:37:28): Yeah, because it’s a bit difficult in the Haskell package ecosystem, I guess, to contribute changes because there seems to be a lot of interdependent parts, and you have to be very careful of, you know, I might need to call this function this way, but then suddenly a lot of other people lose that functionality, right? You said you’re not so much of a performance guy. What do you look at when you – because you’ve written a lot of big applications, so you obviously have some sense of performance by now, right? So is there anything that you could share? When should we use strict things? Should we use Template Haskell all the time?
MS (0:38:04): Good questions. You did actually bring up lazy evaluation earlier, so why don’t I knock that one out? I don’t believe lazy evaluation was a good idea, at least as a default. Having lazy evaluation available is useful, but as the default, I think it’s caused more harm than problems it’s solved. I don’t say that primarily from a performance standpoint. I know that there is absolutely a performance overhead to it. It mostly comes down to a correctness standpoint. And there you get into a little like, is a space a performance issue, or is it a correctness issue? I’d call it a correctness issue. But when we start talking about those kinds of things, I would say avoiding laziness overall is one of those defaults that we should be going with. In fact, most of my coding recommendations in Haskell come down to changing the defaults that you’re working with. So if the default is, I would say default to not using any partial functions, default to a Prelude that doesn’t have partial functions or uses better data types like text instead of string, and things along those lines. It is unfortunate. I think it’s actually one of the things that’s holding Haskell back still, is the fact that out of the box, the Haskell experience is missing a lot of the great things we could be having. Some of the warnings that we could have on by default would be wonderful things. I think partial field access are still provided by default with no warning. This just came up the other day at the conference I was at last week. So those kinds of things, I think getting all of those in place. But all of those things are not around performance. All of those are really built around the idea of correctness of the code.
On top of that, choosing the correct data structures to be using, that would be the next step I would recommend. Make sure your data structures are well set up. And then for the most part, I don’t actually worry about performance beyond that. When I’ve had performance issues, like most developers, we can profile, we can find out where they’re located, and resolve them. Most of the time, for most applications, I’ve seen most people writing, it’s not going to end up being a problem with Haskell if you end up architecting your code correctly. I know we always like to say, “Hey, maybe we’re in the ballpark of C++.” I don’t think Haskell really is, but I think even if we’re talking about two or three times slower than C or C++, that’s still very respectable performance, especially given what other languages are like today. I would take that trade-off any day of the week. I’d be happy to be 10 times slower if I get to use STM.
MPG (0:40:19): Yeah. So you mentioned Conduit earlier, and I think it’s a very interesting library. A lot of our listeners haven’t used it, I guess. So could you kind of just explain the idea a bit?
MS (0:40:31): Sure. And it actually ties in really nicely with the idea of lazy evaluation, or at least lazy IO. So why don’t I start off with lazy IO? Many people in the Haskell world have used lazy IO of some kind at some point. You’re going to use readFile out of the Prelude. Magically, you have a two-terabyte file just sitting in memory, ready for you to work with. Everything works fine. You add up, you count up all the letters in the file, and suddenly everything crashes, and you don’t know why you ran out of memory. That doesn’t seem to make sense. And the problem is that lazy IO is a cheat. Lazy IO is magically interleaving things in between actions in between your pure code. Lazy IO is also giving you an abstraction that doesn’t let you really know how much memory you’re using at any given time. It falls into the painful situation where seemingly innocuous changes to your code cause fundamentally different behavior. Also, the fact that exceptions can just pop out of pure code, those two things together, lazy IO is something to avoid.
So once you’re at the point of, “Cool, I’m not going to use lazy IO,” what am I going to use instead? Well, we can start talking those things through. One possibility would be, I’m going to manually start reading chunks out of a file. I’m going to open up a file handle. I’m going to live in IO. I’m going to write something that grabs a chunk of data, sticks it in a ByteString, moves on. Now you’ve basically re-written a very poor version of C. I know how to write C. I don’t like writing C. Why would I want to do that in Haskell? Instead, what I want to be able to think about is the abstraction. The abstraction is, there is a file. The file represents a stream of bytes. And how do I talk about that stream of bytes? That’s what Conduit comes in to solve. It is a coroutine-based streaming framework, and the idea is that it’s built up of one single internal data structure. That internal data structure essentially represents a transformation from stream of one type of data to a stream of another type of data. It can live in any kind of a monad. So we automatically get purity, or we can get side effects or anything else we want to deal with. It also gives us these really cool lack of side effects.
Imagine I want to write some kind of an algorithm that’s going to take all the data in a file, and—silly example—I’m going to count how many periods there are in the file. Okay. How do I do that? Well, lots and lots of easy ways to do that. Cool. I’ve written that. How do I test this? Well, one possibility is you just write a bunch of files to disk, and then you read the files, and you count it. But wouldn’t it be cool if you could test that in a pure fashion, just like we do with everything else? That’s one of the things you can get with Conduit. I could write what we would call a sink, something which is able to consume stream of bytes and is able to perform a calculation on it. I could tie this together with QuickCheck and generate random input and throw it at this thing and make sure that it’s actually performing the right thing. And then at usage site, I could take that exact same function, which previously was a sink that lived in any arbitrary monad, and now I can concretely apply it to an IO-based monad. And at that point, now I’ve done all of my testing and purity. I got all the advantages of purity based on the type signature. I even know that the sink can’t possibly be performing any IO on its own, and yet it’s able to work perfectly in an IO pipeline without any kind of adapter, without any kind of a slowdown.
MPG (0:43:46): And then it is used everywhere in Yesod, right?
MS (0:43:50): Pretty deeply. Yeah.
MPG (0:43:53): I think it’s a very – because it seems like, yeah, it’s like an abstraction around laziness, right? You kind of made it more explicit, so now you can actually reason more about it than just whatever magic Haskell does, right?
MS (0:44:03): Right. Since you put it that way, that would fall into where I say lazy evaluation as the default wasn’t the right decision. Here with Conduit, you can see opt-in laziness is very powerful. And it’s not like Conduit invented this. It wasn’t the first library that did coroutines, but coroutines as a general idea. Sure, we’re able to opt in, throw in some kind of closures, get some laziness, and get all the benefits of laziness without having to pay the correctness or performance costs in normal code.
MPG (0:44:33): I remember, yeah. It was one of those libraries when I came to Haskell. I’m like, “Oh, wow, it’s cool.” But it also forces you to kind of structure your code in a way that is going to make sense, right? Whereas, like you said, otherwise you just write bad C, and then there’s no reason to use Haskell anymore, right? Yeah. No, very cool.
MS (0:44:55): And I mean, we still have some – Warp, for example, early versions of Y in Warp did use Conduit directly, if I remember. I think that’s true. I might be mistaken on that. I think we used Conduit. Maybe it was just resourceT. In any event, we’ve used some of those libraries at some points in development for some of the lower-level stuff both for performance reasons and because of the dependency game, which everyone likes to play. We’re going to remove yet another dependency, got rid of Conduit, and everything in Warp at this point is much more low-level, working directly with handles or sockets. I think it’s sockets everywhere, all along the way. So even in the Haskell world, it makes sense sometimes to write code like you’re writing C. And as much as I said, I could just go write C. It’s just worse C. Haskell tends to be a better version of C most of the time also. I much prefer getting a ByteString instead of a char pointer and hoping that I know what the hell that means.
MPG (0:45:51): Right. So for me, the last question, I mean, you talked about things you would like to remove from Haskell as a language because you’ve written a lot of packages, but what is a package that you wish existed in Haskell if you didn’t have to write it yourself?
MS (0:46:06): This’ll be a little bit of a cop-out, I guess. Some of the packages from Rust that I absolutely love, probably the biggest one would be Serde. I think it’s the best-in-class serialization and deserialization library out there, if we had something like that in Haskell. If you look at Aeson, it does do some of that kind of stuff with generics driving with Template Haskell. It gets some of the way there, but it’s not nearly as ergonomic, and that’s the big question. It’s not nearly as ergonomic as SerDes.
Another comparison point, which is really interesting, is actually OptParse Applicative versus Clap. I think overall Clap is the easier one to work with and the one that most people would – I mean, most unaffiliated people who’ve never worked with one of these libraries, they’d probably feel more comfortable with Clap out of the box. OptParse Applicative, though, still the applicative interface, provides so much flexibility to be able to do things in different ways. And I guess overall that’s still one of the biggest advantages Haskell has. I like to tell people about using applicative together with the concurrent new type wrapper from the concurrent pack, the Async package, the ability to just spin up a completely concurrent algorithm using those weird symbols—the dollar sign or the tie-fighter or whatever we call it, splat. I’ve heard so many different terms for it, but using all of those different applicative operators, that’s like magic. It’s actually almost too much like magic, and people don’t trust us when they start seeing it, but it’s truly powerful. OptParse Applicative falls into the same category. But Serde, I think, would be the one that I’d say, “I wish we had that in Haskell.”
MPG (0:47:47): Right. So we talked about compilation times. One of the things that people really love is Template Haskell. You use it quite a lot in Yesod, right? You have all these HTML combinators. It’s very cool. So could you talk a bit about, like, what’s your experience with Template Haskell?
MS (0:48:07): So my experience with Template Haskell is probably that I overused it. That’s the reality. When I first started in Haskell, that was the first language I used, that I had heavy metaprogramming capabilities, and I probably saw a hammer and everything looked like a nail at that point. I’m not sure if I would design things exactly the way they are today if I was to do it over again. On the flip side, I sometimes look at some of the later libraries that are in the Haskell world and think, “Man, I wish I still had the eyes of a newcomer like I did with Yesod.” I’m very conflicted on it because I look at the Yesod routes and text, for example. It’s clean, it’s short, it’s concise. Almost anyone can understand it looking at it the first time, even if they’ve never touched Haskell or most web development before. And on the other hand, it limits flexibility. The ability to just write normal Haskell code as opposed to the special templates and texts, that would give us a lot more flexibility, and I think that’s probably been my biggest learning overall, that there is this inherent trade-off between, on the one hand, wanting to give the user as much power as possible, and on the other hand, making the newcomer experience as pleasant as possible. Anytime we’re able to figure out a way to have our cake and eat it too, that’s best. I’m not convinced that I did that in every case with Yesod.
So Template Haskell in particular, not only is it this syntactic thing of did I get too far away from the language by doing all of these special templates, there’s also the question of compilation speed, which in my opinion, for the most part, I’d rather have slower compilation and a nicer experience. But Template Haskell really pushes that right up to the limits, and maybe it’s worth taking a different kind of approach. And then the other big one is people feel scared. When you just have this one little line that generates thousands of lines of code—I hope not thousands, but generates huge swaths of code—a lot of the time, people don’t feel comfortable with it. And since I’ve only said positive things about Rust up until now, I’ll say Rust has exactly this problem, but on steroids, because Rust macro usage is way more than it is in Haskell. If something breaks in a macro in Rust, a lot of the time, you have no idea what exactly the code was that got generated or why things broke. Now they have some nice tooling in the Rust world that we may want to steal a little bit of, like cargo-expand shows you things, and it’s a lot easier to work within the dump splices, which is the equivalent in the Haskell world. So those kinds of things make it easier. But the only reason all those tools exist is because there is a pain point in the first place.
So would I do things differently today? Potentially. Very conflicted. Fortunately, I don’t have to make that decision because the decision has already been made. The code is written, and I’m not going to rewrite it at this point. But anyone else is coming along and thinking, should I go more in this DSL-y kind of direction and leverage Template Haskell? Be a little careful about it. I think my favorite thing for Template Haskell is avoiding error, specifically error-prone boilerplate. If you’re able to use Template Haskell to eliminate the possibility of stupid bugs, like, oops, my serializer and deserializer accidentally use the wrong two different field names, I did the British versus American spelling, to use an example from earlier—if those are the kinds of things you’re using Template Haskell for, awesome. Go for it. I think it makes a lot of sense. But even there, you still do need to be a little careful around the compilation times.
SF (0:51:28): I have one last question. So you’ve done a lot of stuff with Rust recently. We’ve done a lot of the comparisons. But at heart, would you consider yourself a Haskeller? I think we need a better word because “Rustacean” just sounds so much cooler. Maybe we should brainstorm that. But would you consider yourself a Haskeller or a Rustacean at heart?
MS (0:51:46): Well, another term we could use is “Haskellator.” That’s actually –
SF (0:51:50): That’s great.
MS (0:51:52): It’s not mine. Steve [0:51:52 unintelligible] came up with that one, as far as I know. That’s still the name of the Wi-Fi network in my house, as it happens.
SF (0:51:59): So at least your Wi-Fi.
MS (0:52:01): My Wi-Fi is definitely still Haskell. Yeah, it’s an interesting question. I think probably I would most describe myself as a Haskeller who writes Rust on a daily basis. I think that’s probably the style of my Rust code, is still Haskell. It’s Haskell written in Rust. I still have the fact that I care more about the correctness and the type safety of my code than I do about the performance. Like this conference that I was at last week, the talk I gave was strongly typed financial software. It was not writing fast things in Rust or using the borrow check or anything. Those are not my interest points. My interest points are still all about, hey, we’ve got this thing in the language. It’s called a type system. It can prevent me from making stupid bugs because I’m going to write stupid bugs. How can I use that to my best advantage? That’s what I think of as quintessential Haskell. So from that standpoint, I probably fall more – morally, I fall more into the Haskell camp than the Rust camp.
SF (0:52:54): Nice.
MPG (0:52:55): All right. Yeah, I think that’s a good takeaway message to end this on, that Rust people should use the type system more, I think. And we should care more about newcomer friendliness. I think that’s one of the biggest barriers to Haskell adoption.
MS (0:53:13): I agree. And one final message for the Haskell community, since there is this talk of people moving on to other languages. I think one thing worth remembering, I think Haskell won, and I know that Haskell winning isn’t about which language are people using today. The ideas of Haskell won. The world that we’re in today is not the same world of 20 years ago. We’re not sitting there talking about C++ object-oriented programming versus Objective-C object-oriented programming and debating the merits of these two different systems. We’re talking about, and we’re barely even talking about whether type systems make sense. At this point, type systems are one for the most part. Yes, I know Python and JavaScript are still the two most popular languages in the world, but look at those languages. Python is adopting type – every project I hear about is using some kind of type checking in Python. TypeScript is the de facto standard at this point for writing JavaScript. We went on that front too. You go down the list of all the things that Haskellers really pushed for in the world. I think the programming world has caught up. I think every language at some point is going to meet the point where some other language took all their good ideas, and we moved on. Haskell’s not there yet. There’s still lots of good ideas from – STM. Somebody’s got to figure out a way to do STM in another language. But overall, I think that’s the thing that people should remember. Like the same way, I can be a moral Haskeller. Well, morally, I can be a Haskeller in Rust. You can do that in a lot of languages today, and that’s a wonderful thing.
MPG (0:54:39): All right. Yeah. Thank you very much.
Narrator (0:54:44): The Haskell Interlude Podcast is a project of the Haskell Foundation, and it is made possible by the generous support of our sponsors, especially the Gold-level sponsors: Input Output, Juspay, and Mercury.