Cameron Gera and Taylor Fausak discuss Drew Olson’s adventures in looping. What should you do if you want to loop forever, but break out of the loop early sometimes?
Episode 54 was published on 2021-10-04.
>> Hello and welcome to the Haskell Weekly podcast. This is a show about Haskell, a purely functional programming language. I'm your host, Taylor Fausak. I'm the Director of Software Engineering at ACI Learning.
>> And I'm here today — Cameron Gera. Um, a Senior Software Engineer at MotoRefi. So um different, uh, title, um, as uh the consistent listeners may know, um, I am a new member of the MotoRefi team. And still very passionate about Haskell, and actually using Haskell at MotoRefi. And still very committed to the podcast, and being able to just bring this to the community. I think it's been a great resource for me to learn. Um, I'm sure I've said wrong things, and that's good because I've learned from those bad things. Uh, but yeah. So, you know, this is a awesome — I'm glad we're getting back to this. We had an interview last week, and then we were off for a week. So I'm glad to kind of — hopefully continue this and get back into a rhythm. Um, but, yeah, so — I mean, I guess enough about me. I guess, we'll — we'll jump right in to today's topic. Uh, it is from issued 283. Um, I believe. If I'm wrong, I'm sorry Taylor.
>> sounds right to me.
>> Um, Cool. Yeah, I've only gone to the website a few times this week to check out the issues. Um, but we're going to be talking about uh, a post by Drew Olson, who is the, uh, lead — er, chief architect at GoFundMe. Um, and he created a blog post just about, uh, looping in Haskell and he called it, very originally, adventures in looping. So going to talk a lot about that today. I guess. Not forever. We promise. But yeah. Anyways Taylor, take — take it away. What — what's this all about?
>> We'll try not to get stuck in an infinite loop while we're talking about this blog post. Um, yeah, so this is an interesting one because in most programming languages, looping is built into the language. You have a while loop or a for loop or a do while, or goto even. Um, but in Haskell we don't have something like that built in. So you have to do it yourself. And the particular problem that this blog post is approaching is: what happens when you're in a loop and you want to break out of it, but only sometimes. So in a normal procedural language, inside of let's say a while loop, you might use break to say: just get me out of this entire loop and we'll continue on with whatever goes after it. Um, that turns out to be, I'm not going to call it hard, but there's no built-in support for that in Haskell. So you have to choose: how do you want to do that? And that's exactly what this post, uh, looks at. So the first step of it is just, how do you loop at all? And anybody that's written Haskell knows that you're going to do either: manual recursion, where you have a function that calls itself when it's done, and then you start over at the top. Or you use something that does that for you, and the typical example is from Control.Monad and it is the forever function. Where you pass it an action, and it will do that action, and then do it again, and then do it again, and keep doing it.
>> You got it. Forever and ever.
>> Hmm, that's a good function name, I guess. It, uh, makes a lot of sense.
>> Yeah. Um, so that's what he starts with. Um, but he pretty quickly runs into a problem with that Right, Cam? Where he says forever, but he doesn't really mean forever. He actually wants to get out of there.
>> Well, he meant forever in the beginning. he realized that connections aren't forever. Um, and so there he was running into some issues and got some unexpected, um, return messages while he was trying to create a Slack bot, um, in Haskell. So, you know, he had great intentions and he was doing everything he could, and then he was like: oh wait, my connection to Slack can get disconnected and Slack is going to actually tell me about that. Um, and we'll get a disconnect message. And so he was getting run — you know, exceptions thrown, um, because it wasn't a valid message type. Wasn't listening for it. Was not exhaustive. Um, well, I don't know, I — I'm not sure what he did with that. He didn't really talk about it. Uh, but, you know, anyways, he, you know, needed to find a way to re — make the connection fresh again, um, without, uh, just like — don't know, doing another set of looping. Whether it be recursion or forever. Um, so he kind of had — had to rethink that a little bit. Um, you know, and I don't think there's — I think there's a couple ways he could have done this. Uh, as far as, like: how does he fix this? Does he go to a manual recursion process? Um, does he choose to, uh — I mean, I feel like manual recursion is probably like the quickest one for somebody to reach to. Like if I get the disconnect, let me throw — you know, do nothing and make the — my, whatever forever loop I'm in restart.
>> And that's actually what he reaches for first. Right? He breaks this inner loop. Because as you mentioned, he now has two loops. He has the outer one that sets up this connection and then the inner one, that handles messages that come on that connection. And occasionally you get a message that will tell you to disconnect. So you break out of that loop and go around your outer loop again and start collecting, or start a new connection and start collecting messages on it. So, what he did was for that inner loop, he broke that out into a separate function where most of the cases end up calling that same function again to loop. But one of the cases, the one that tells you to disconnect does nothing instead. So that is effectively breaking the loop and then the outer loop can continue. So, like you said, I think that's a pretty, um, unsurprising approach to this. So it's natural that that seems to be the first thing that he reached for.
>> Yeah. And while it's clear what's happening, there's some repetitive code there, right? Because you're calling — anything with with recursion, you know, it's going to call itself. And the fact that his type he was using had two successful cases and one failure case. It was, know, having — you could loop from two different places. Which isn't bad, but, you know, there's gotta be a better way. Uh, and so, you know, he also takes some time to talk about the forever function. Um, it actually is — can work with anything that is applicative. And the — really what happens is it says: okay, hey, I'm not going to care about what happens on the right side. Or, well left I guess. And I'm going to — yeah, the first thing I'm going to execute the second thing. And how it, like, recurses. All the time. Um, So it really just calls itself.
>> Yeah. And actually he points out that this is — and you said it can work on any applicative. It doesn't actually require a monad. And that is telling because with applicatives you can't make a choice based on what the previous result was. That's not a power that the applicative type class gives you. So the type signature tells you that it can't depend on the result of the first thing. Whereas if it was a monad, it could depend on that. Cause that's the thing that monad adds on top of applicative. Anyway, I just thought that was worth noting.
>> No, I think that's great. Yeah. And, um, yeah. So he kind of talks more about the forever function and what that means. And, um, you know, just kind of laying the groundwork for and explaining what the forever function uses. Um, but with this function, it has, with the applicative star, um,
>> Star greater than?
>> Yeah. Um, that function, or that operator, um, will short circuit if something, um, fails. And so, you know, think about this working with applicatives. And, you know, also the maybe monad. Like that — you can kind of play around with that and see what happens. Where you can say: okay, hey, I got, um, you know — I have a nothing value and a just value. You know, and then followed by a just value. Well, it's going to short circuit and say: oh, you got, you gave me a nothing in this first case. We're done here. I can't, uh, I won't evaluate anything else.
>> Right. It doesn't matter what's on the right. As soon as you hit a nothing, that's what the whole thing is going to evaluate to. I like to think of it — you said short circuit, that makes me think of boolean expressions. Where if you and a bunch of things together, as soon as you get a false, it doesn't matter what all the other stuff is, it's going to be false. So there's no sense to evaluate That's how I think about this one.
>> Yeah, yeah. I think it's good. And, you know, I think, you know, when, when we were thinking about what we want to talk about today, in the podcast — this one, this article stuck out to me because, you know, I'm — you know, as I'm starting a new role, we have a greenfield project we're doing, and we're going to be doing some, some listening for some events. And so, you know, I'm going to expect my service to continually look for something new. And if there's nothing, it can just wait and try again. And so, you know, I'm not sure yet if there's going to be something that may need to be short-circuited or, you know, evaluate, er — um, kind of reconnect to something more or less. Um, as this example is showing. But you know it kinda gave me that refresher on, you know, what forever was doing and, and how it worked. And, um, you know, gave me some, some tips and tricks as I'm, you know, bringing — introducing this function, as well as probably explaining and working with the team on this function. And so, know, that was kind of a side note, but that's one of the reasons that I was motivated to kind of talk about this today a little bit. Um, But yeah, so. We talked about how applicative — or how, the forever function works with maybes. Or really the asterisk greater than sign that's from applicative talked about that. But, um, he doesn't, er — Drew doesn't just kind of stop there. He says, well, you know, there's something, you know, my, our experience at ITPro as well was, you know, using this other thing called MaybeT. So the trans — the maybe, uh, maybe transformer monad, right? Is that what that stands for?
>> Yeah. Normally, if there's a T at the end of a type name that suggests that it is a monad transformer.
>> Right. And so, you know, he kinda goes into this to kind of give you the, um — help you be in the monadic state. Um, for this evaluation.
>> Yeah. And like him, I don't think we need to go into a full explanation of what monad transformers are. That's a little bit out of scope here. But the quick and dirty explanation is that: the monad transformer lets you add another capability to your monad stack, or whatever context you're operating in. So with MaybeT that means, in addition to whatever you're doing, whatever else you're doing, you can express optionality. Something either will be there or won't be there. And for MaybeT, it does that behavior we were talking about earlier, where as soon as you get a nothing, it will stop. And that sounds really appealing, cause that's what we want to do here inside this loop.
>> Yup, and so, you know, kind of, as he, you know, explains that a little bit. Without going into too much detail. Uh, you know, he just kind of said, you know, here's some examples of using MaybeT. You know, contrived, simple to understand, um, things. And I'm not going to read them out loud because reading code on a podcast just doesn't sound like fun. But, you know, the long and short of it is: with MaybeT there's a way to short circuit a forever loop.
>> Yeah. And it's a handy little function that is actually polymorphic, but in this case: mzero will break you out of whatever loop, if you're in a loop. Or break you out of your MaybeT transformer. Or stop your maybe uh do block or whatever it is. And the name isn't super evocative in the same way that forever is as a function name. So you may want to, if this is something you do frequently in your code base, you may want to provide an alias for it and call it exit or break or get me the heck out of here or whatever you want to call I
>> Or just don't. You we have like, do notation and then there's don't notation.
>> You know, those are, those are fun things to, uh, to work together.
>> But mzero does exactly what we were discussing earlier of: if you're in MaybeT, and in this instance it's IO, so MaybeT IO. mzero will work like break and short circuit and stop and not do any of the other stuff. So if you're inside forever, and you do a bunch of things, and then one of your branches says mzero. When it hits that branch, it's going to break out of that loop, which is exactly the behavior he was looking for here.
>> Yeah, I just, I just see it like. Every time this happens. It just like bust out into like some high school musical song that. I like breaking free or something like that. Uh, but yeah, so that really honestly wraps up the blog post. I did want to kind of, uh, reach and kind of talk more about maybe some of the other looping, um, options that are out there. You know, I know, at ITPro we've used a few. And, you know, I was just kind of curious maybe what some of the pros and benefits were to that. And keep it short and simple because, you know, we don't want to keep these people in an infinite loop forever.
>> So I think the MaybeT approach is a good one. And in this blog post Drew links to another blog post by Gabriella Gonzales. And in that post, uh, it talks about MaybeT, but it also talks about EitherT, which is the monad transformer for eithers. And that can do the same thing, will it — where it will short circuit and stop as soon as it hits a left instead of a nothing. And that can be useful if you have a program that normally loops, and when it stops looping you need to know why it stopped. Or you want to communicate something like that. So imagine that there was like a disconnect message that you may hear that's expected. But then maybe there's also like a disconnect with some extra metadata, or maybe like a crash or something like that. And you want to communicate back the manner in which you stopped looping. That's when you might need something like EitherT.
>> Gotcha. Okay.
>> But fundamentally — fundamentally the two approaches are the same. Where it takes advantage of this short-circuiting behavior to stop in certain circumstances. Um, and in terms of other approaches, I wanted to loop back to the first one we talked about, which is manually recursing by pulling out a function definition and calling yourself so that you do that uh loop yourself. And then in some branches not doing that. That's often the one that I reach for when I end up in this situation. And I can recognize that it's not very, um — it's not a very good approach. It's not like — Yeah, not fancy. It's not nice. But it works. And you don't really have to think about it. And it makes tracing execution a lot clearer. And you don't have to pull in monad transformers and potentially — potentially explain them. And I feel like this circumstance doesn't actually come up that often, in the code that I write at least. Where I have some infinite loop that I sometimes need to break out of. Usually when I have an infinite loop, it's actually infinite. Or, um, you know, the one or two places in my entire application where I need to do this type of looping, it's fine to write that recursion manually.
>> I don't even know what to add to that, but yes, well said, Taylor. That's, that's really what I've got to say there.
>> Um, and then I wanted to mention: for IO and for many other monads, I think there is another approach. Which is to use exceptions. And this is maybe a little less satisfying than the MaybeT or EitherT approach.. But what you can do is have your forever loop. And then wrap that in something that catches a like early termination exception. Which is some data type that you can write. And then in the branches where you want to end early, where you would call mzero or where you would not call that recursive function. Instead, you can say throw early termination exception. And that will break out of the forever loop and be caught by that exception handler. And then that exception handler probably will just do nothing with that. And then it'll continue, you know, with whatever else is going on. So, uh, I think practically the end result is the same as MaybeT or EitherT. But it gets to use machinery that you probably are more used to from other contexts, of throwing and catching exceptions.
>> Yeah, and I feel like it could to give you more information. If you had, you know, like, hey this failed. We're terminating you this because of X, Y, or Z.
>> Yeah. That's like the EitherT cause EitherT can communicate the exact same amount of information. It's just in a little bit different way. And I think if you're already in IO or something that can throw exceptions. You might want to go the exception route. But if you're in a pure monad, something that can't throw exceptions, or you don't want to for whatever reason, then MaybeT or EitherT are good ones. And I'm sure there are more approaches to this problem, but those are the ones that I'm aware of. And those are the ones that I would reach for in this circumstance.
>> Awesome, well yeah. And I think that really does it for the podcast this week.
>> Short and —
>> Short and sweet. Kind of uh probably a breath of fresh air for most people. We've rather long lengthy conversations the last, uh, three or four episodes. So you know let you guys get back to your day. Um, but yeah. Thank you for listening to the Haskell Weekly podcast. Um, I've been one of your hosts, Cameron Gera. And uh, if you have more interest in, maybe what's out there for Haskell Weekly you can follow us. At — uh, on Twitter. And any other social platform maybe. Just call Taylor, really. That's all you gotta do. Google him you'll find him. Uh, but yeah. Haskell, uh — on Twitter we're @HaskellWeekly, or you can visit us on the website at HaskellWeekly.News. And that's where you can find all the up-to-date information on Haskell and what's going on with community. So please check it out.
>> This week as every week, we are brought to you, sadly, not by our employer anymore, but by my employer, ITProTV, an ACI Learning company. And they would love to extend to you and offer for 30% off the lifetime of your subscription. Just head over to ITPro.TV and type in uh promo code HaskellWeekly30 to get 30% off the lifetime of your subscription.
>> And Cam, I didn't get to say it at the top, but congratulations on your new gig at MotoRefi. Super exciting. And I'm looking forward to our future conversations about how we use Haskell here versus how you use Haskell there. Think it's some good stuff in the — in our future.
>> Oh, yeah. Let the debates begin. Just kidding.
>> But for now —
>> By Taylor every time. you guys
>> Not so sure that. For now that'll do it. And, uh, we'll see you next week.