Algorithms + Data Structures = Programs - Episode 157: The Roc Programming Language with Richard Feldman

Episode Date: November 24, 2023

In this episode, Conor and Bryce interview Richard Feldman about the Roc programming language, what qualifies a language as a functional programming language (and whether Rust makes the cut) and more!...Link to Episode 157 on WebsiteDiscuss this episode, leave a comment, or ask a question (on GitHub)TwitterADSP: The PodcastConor HoekstraBryce Adelstein LelbachAbout the Guest:Richard Feldman is the creator of the Roc programming language, the host of the Software Unscripted podcast, and the author of Elm in Action from Manning Publications. He teaches online courses on Frontend Masters: Introduction to Rust, Introduction to Elm, and Advanced Elm. Outside of programming, he’s a fan of strategy games, heavy metal, powerlifting, and puns!Show NotesDate Recorded: 2023-11-13Date Released: 2023-11-24Software Unscripted PodcastThe Roc LanguageHaskell LanguageElixir LanguageSoftware Unscripted: Gradual vs Static Typing with Jose ValimInterview with Senior Rust Developer in 2023 (YouTube Video)Rust Iterator::mapRust Iterator::filterZig LanguageThe Essence of Functional Programming by Richard Feldman #FnConf 2022Rank-N Types (Higher Rank Types)Intro Song InfoMiss You by Sarah Jansen https://soundcloud.com/sarahjansenmusicCreative Commons — Attribution 3.0 Unported — CC BY 3.0Free Download / Stream: http://bit.ly/l-miss-youMusic promoted by Audio Library https://youtu.be/iYYxnasvfx8

Transcript
Discussion (0)
Starting point is 00:00:00 I what I have found and what other people I know have found who have spent a lot of time with Rust after having spent a lot of time with another functional language like Elm or like not necessarily Haskell but Elm or Scala or something like that is that if you try programming Rust in a functional style like a really functional style you're going to have a bad time like it's just better to embrace the imperative and just use it like an imperative language. Welcome to ADSP, the podcast, episode 157, recorded on November 13th, 2023. My name is Connor, and today with my co-host, Bryce, we continue our interview with Richard Feldman. And in today's episode, chat about the Rock programming language. What qualifies a language as a functional programming language?
Starting point is 00:00:50 Whether Rust makes the cut and more. Anyways, episode 157. We are back. I mean, we never left, but the listener had to wait a week. So sad. And we're back with Richard Feldman. And now is the part, I think, that we are going to transition to talk all about the ROC programming language.
Starting point is 00:01:12 That's R-O-C. And I will just throw it over to you, Richard. Maybe you can start by telling us what is the motivation for this language and sort of what paradigm you're aiming for it to fit into. Because as you know, there are many different paradigms, many different languages, many different use cases. And, you know, there's lots of programming languages coming out these days. But what's the goal that Rock is trying to achieve?
Starting point is 00:01:34 Sure. So I guess, I mean, there's the story of like how Rock came to be. But I guess the way that I would sort of pitch it to someone, you know, listening who maybe is familiar with C++ or, you know, is interested in other languages, is that it's a fast, friendly, functional language. That's sort of our tagline. So fast in terms of performance, like runtime performance, not as fast as C++, but fast among sort of automatic memory managed language. And also fast in terms of compile times. Both of those are very important to us. Friendly in terms of user friendliness. So
Starting point is 00:02:13 that's both in terms of we try to make the compiler have really nice, friendly, helpful error messages. And also that we try to ship with a lot of tools built into the binary. So you've got testing built in, code formatting, package management, all that the binary. So you've got testing built in, code formatting, package management, all that good stuff. So you don't need separate tools for all those different things. And then functional because it is a single paradigm functional language.
Starting point is 00:02:36 How's that? So my first question is a fast, friendly, functional programming language. The first thing that my brain thought of was, ooh, does he think that Haskell is a slow, friendly functional programming language. The first thing that my brain thought of was, ooh, does he think that Haskell is a slow, unfriendly functional language? Like, so how does Haskell and Scala and maybe Elixir, those are probably like the three most popular, you know, I'm probably going to get flamed a little bit,
Starting point is 00:03:00 but like, you know, depending on the ranking lists, those are typically in the top five. You know, maybe F-sharp is up there as well, depending what community you're in. How do those, are those considered relatively compared to rock on the slower end or more unfriendly end, or how would you like juxtapose those languages versus rock? Sure. So I would say, I don't think it's unfair to say that rock puts a lot more emphasis on user friendliness than Haskell does. Without making a value judgment on, you know, like people's preferences, just if you look at like
Starting point is 00:03:32 an error message that comes out of Rock's compiler versus GHC, it's just, I think it's a lot friendlier, generally speaking, in terms of Rock. Now I should say GHC is much more mature. So one possibility is that the error message you'll get out of Rock's compiler is there was a compiler crash because we are not even at version 0.1 yet, which I don't think you should expect from GHC nearly as much. But assuming you do actually get a normal error that says syntax error or type checking, type mismatch or something like that, we very much take very direct inspiration from Elm on how we structure our error messages. And I think Elms are just the most friendly that I've ever encountered. And that includes Rust. The entire Rock compiler is written in Rust. So I have a lot of extensive experience with both Rust and Elm, and I still find Elm's error messages to be significantly friendlier than Rust's. But so comparing Rock to Scala, I would say that rock should run faster than
Starting point is 00:04:28 the Scala. Um, we don't have like direct benchmarks to back that up in significant part because those are kind of hard to do on a whole language level. Um, but also because we don't really have any substantially large rock code bases yet. So, uh, and also we haven't realized all of our sort of ambitions with regards to performance optimization. We've done a lot, but not as much as we plan to do. And then comparing to Elixir, I would say the main thing is that it doesn't really have to do with being fast or friendly or functional. Well, I guess you could make the claim that
Starting point is 00:05:04 Rock is more strict about being functional than Elix guess you could make the claim that Rock is more strict about being functional than Elixir is, in the sense that Rock is purely functional, so there is no mutation, there's no side effects, whereas Elixir certainly allows side effects. But also, Elixir is, for the moment, dynamically typed, and Rock is statically typed. Depending on who you ask, you could say that is more or less user-friendly. But for me as a user, I definitely like having a language like Rock that has a really simple static type system, so certainly much simpler than Scalas or Haskells, and also one that has full type inference, so I don't need to annotate anything if I don't want to. All those things are true, and so, you know, I guess depending on your
Starting point is 00:05:40 preferences, you might say one of the, you might say, despite the fact that it's got full type inference, I would still prefer a dynamically typed language. And if so, then you would probably prefer Elixir. But for me personally, I like having not only the, having the types available, but also having a language that's designed around the types. I think there's a really big difference. And I've talked with Jose Valim on my podcast, Software Unscripted, several times about types and Elixir and stuff like that. So check out those episodes if you want to hear more about that specific conversation. But I do think there's a significant difference between having a dynamic
Starting point is 00:06:14 language where you add types after the fact compared to a language where everything in the entire ecosystem is always designed with types from day one. Interesting. Yeah, I definitely love, I mean, I think Haskell was pretty eye-opening the first time, because I think a lot of people conflate explicit types and like strong and static typing. Like they think, oh, if I have this really strong static type system, you know, I'm going to have to explicitly write all my types everywhere. And then you see Haskell and it'll infer the whole type signature of your function, you don't have to do anything. And it's like, well, you can have the best of both worlds, you can have a very, very strong static type system. And you can have all your types inferred. And where you want, you can
Starting point is 00:06:54 sprinkle them in to make sure you're doing things, you know, the way it is, but it's almost like a failsafe that if you if you don't want to write them, you can get away without writing them, which is pretty phenomenal. So I guess my next question, too, is, you know, a lot of folks I have heard, and you said that you've got a lot of experience as Rust, they end up going from, you know, there's some joke, or I'm not sure if you've seen, we'll link it in the show notes. I hope most of the listeners have seen it. There's a Rust developer interview, YouTube video by this guy. I can't remember if he's wearing like a blue wig but it's a seven minute interview about it's like it's a it's a sketch where he's you know being interviewed about why he loves rust and it is absolutely hilarious and one of the questions
Starting point is 00:07:38 they ask because everything's like a joke it's like well for someone who's uh doesn't not familiar with haskell how do you recommend them learning rust? And then his answer is deadpan, like, well, what are you talking about? Go learn Haskell first. Like, the first step to learning Rust is going and learning Haskell and then coming and learning Rust, and then you'll love Rust. And so it's this just general idea that Rust is a language that satisfies a lot of functional programmers, and it's quite fast. So is the main difference, it sounds like, is that Rock is aimed at being a friendlier version of Rust? Because Rust does have pretty good error messages
Starting point is 00:08:12 most of the times, depending on exactly what you're doing. Or so like, what would you say, you know, for someone that's evaluating languages for some, you know, speedy, they want a functional language, why would someone choose Rock over Rust? Well, I think if you want a speedy functional languages uh a speedy functional language a good reason to choose rock over rust is that it is a functional language um i i know i've heard people say that rust is a functional language i it doesn't make any sense to me uh rust is like
Starting point is 00:08:40 one of the most imperative languages i've ever used and like okay they have like map and filter and stuff like that like these things that also occur in functional languages like they have first class closures and so you can uh do some amount of higher order stuff but like i coming from a functional programming background the first thing that i tried to do in rust when i was um making the compiler for rock was uh i say I say it like it's in the past tense, but actually this is still the parser that we use today. I did everything with parser combinators because that's what I was used to. And I kind of assumed that A, Rust would just sort of magically take care of the performance for me. In fairness, the parser seems to be plenty fast in this style.
Starting point is 00:09:24 I have some plans to like rewrite it using SIMD and some other things to make it a lot faster, but nevertheless, it's certainly not the bottleneck in our compilation pipeline, which is already fast by a lot of languages standards, including Rust. But what I have found and what other people I know have found who have spent a lot of time with Rust after having spent a lot of time with another functional language like Elm or like not necessarily Haskell but Elm or Scala or something like that is that if you try programming Rust in a functional style like a really functional style you're going to have a bad time like it's just better to embrace the imperative and just use it like an imperative language I mean we could get into sort of more details on that if you want. But like, to me, if you want to try to do the whole, like, I'm not going to mutate
Starting point is 00:10:10 anything, I'm not going to do side effects thing. It works really nicely if you have a language that's designed around that, like Rock or Elm or Haskell. But if you try to do that in Rust, I think it's just, you're really swimming upstream. Interesting. And it was in using parser combinators to implement your parser that that was the first time that you hit this wall. Well, I literally encountered a Rust error that I have not seen before or since, which was the compiler basically said recursion limit reached. Like you have done too much recursion in your Rust program. I give up. Just stop. Do less of that.
Starting point is 00:10:44 I cannot compile your program because you're doing too many. And like in parser combinators, once you get past a certain size, that sort of happens normally. Fortunately, thankfully, I discovered that if I converted some of my higher order functions to macros that were exactly the same thing,
Starting point is 00:10:59 but that sort of took them out of the type system. This was not just about inlining, by the way. I tried making them inline, no dice. The problem was that the Rust type checker could not handle the amount of like recursive structures that I was using there. And so just turning the functions and like doing nothing else,
Starting point is 00:11:17 but swapping out the top line. So it was a macro instead of a function, keeping the entire implementation the same. That fixed that problem. Then I was able to continue finishing the parser. But like there was a commit where I was just like, oh, like now what I and I kind of thought I was like, have I just dug myself a deep hole and now I have to go back and rewrite the entire like the parser is pretty big at that point. I thought I was going to have to rewrite the whole thing until I found the macro trick. So now if you look at the parser
Starting point is 00:11:41 code base for rock, you see because it is still in this parser combinator, half macro parser combinator style, you see a bunch of things that are functions that a bunch of things that are macros. And the reason for that mix is just that, like I said, Rust is just not set up for this style of programming. Yeah, it's interesting, actually, that you say that because one of I've not have anywhere near as close the amount of Rust experience as you do, but I've written at this point, I don't know, at least several thousand lines of code. And I was trying to write an equivalent of a C++ mini library that I wrote that basically just provides a bunch
Starting point is 00:12:16 of combinatory logic combinators. So if you're familiar with like flip from Haskell, which is the C combinator, just things that are basically function manipulators. And I ran into this issue because I don't fully understand lambdas and closures and rust because in C++, like it's actually pretty straightforward. You know, you need, if you get into the details of it, you know, depending on how you capture your capture variables, it'll, you know, eliminate the trivial copyableness of your lambda. But in general, you've got, you know, eliminate the trivial copyableness of your Lambda.
Starting point is 00:12:46 But in general, you've got, you know, generic parameters, you capture stuff, you compute stuff, and you're done. Whereas in Rust, you've got like, lowercase fn, uppercase fn, uppercase fn mute, uppercase fn mute once or fn once. And depending on how you like move all your lambdas and closures around it blows up pretty quickly and i didn't know what i was doing when i was trying to compose basically just like closures that were returning closures and capturing different stuff but i asked a chat gpt and it said like oh it's having a problem deducing like the type of this because you're not using enough types uh and until it's used it's not going to be able to infer what you want. So it said the workaround is just like use this macro. It's the equivalent of what you're trying to do, but like it doesn't need to know about the types.
Starting point is 00:13:31 So it's funny that you were trying to do some functional stuff and then the solution was use macros. And I haven't, like I said, done the, to the same extent of work, but, and also too, part of my problem is that I don't fully understand the ins and outs of all the different, you know, closure. I don't know if they're trait types or what they are. All those fun, fun, fun, fun, fun things. And like putting the dine keyword, I don't understand that comprehensively. I understand the basics, but when I get some error that says, oh, you didn't satisfy this bound on this trait for this thing.
Starting point is 00:14:00 I'm like, yeah, okay. Well, I don't know how to fix that. But interesting. So your overall statement is that if you're looking for a functional language, it is a misunderstood impression that you can actually do, like, really functional stuff with Rust. You can do functional-esque stuff, but no more than, like, other imperative languages, really. Yeah, I mean, the way that I think of it is that Rust is a really nice imperative language i'm a big fan of rust um and also rust has some things in it that are often found in functional programming
Starting point is 00:14:32 languages like to me i don't think that higher order functions are an inherently like functional paradigm thing they're a thing that sort of naturally occur in functional languages and maybe at a higher uh rate than you would find in imperative languages traditionally, but there's no reason they have to be. You can just have them in any language. What languages don't have higher order functions? Today?
Starting point is 00:14:56 C? Well, you can pass function pointers. Does that count? I think for C there's definitely an argument that that uh at least it's not not a properly supported um c plus plus i think it's a slightly different story because the c plus plus world increasingly you have function objects um and increasingly that has become the way that we pass um functions and in fact in some cases even the way that we pass functions. And in fact, in some cases, even the way that we define functions.
Starting point is 00:15:28 But I guess C and C++ is an example of a language that didn't have that as built in from the ground up. But can we think of a language that just really doesn't have them? That's hard. Does Zig? I don't know enough about Zig. Zig, I think, is in the same boat
Starting point is 00:15:49 as C, where if you would count C, then I think you would count Zig, but Zig doesn't have closures. If you consider... Now we're going to get into definitional semantics. Do you want to count a function pointer as higher order? Do you want to count a function pointer as higher order do you want to count a function that is a conceptually if like something more than a
Starting point is 00:16:09 function pointer but doesn't capture does that count i've always thought of higher order function is really the essence of it being i am going to tell this function about some arbitrary logic that i want to run and so i guess i mean then again by that definition you could say that like ruby's blocks count as higher order functions which i i wouldn't consider that so yeah i guess it's a sort of where do you draw the line question but oh wait so so does ruby not have so is ruby's blocks the only way that it has to pass it's another way to pass like a function ruby also has i mean lambdas that you can pass around but um blocks are a lot more commonly used. I actually never really understood why that was. That's just something I observed in my relatively limited experience doing Ruby code. that a language to for it to have higher order functions that you need to be able to treat a function that you've defined as an object or as a value that you can pass around
Starting point is 00:17:13 i don't think so i mean to me personally i think okay so so i mean taking a step back i may have an unusual view on this but generally my view of the functional programming style like what does it mean to do something in a functional style it just means and nothing else avoid mutation and avoid side effects and to me functional languages are languages where that's the primary way that you're supposed to do things is you're supposed to avoid mutation you're supposed to avoid side effects and if there is support for doing things other than that it's supposed to be a small minority of what you're doing and not i think i think one of the problems is that um the mainstream imperative and multi-paradigm
Starting point is 00:17:59 languages like c++ and rust have uh essentially hijacked the meaning of functional. Like, the reason I asked the question about, like, can we think of a language that doesn't have some form of higher order functions is, like, that doesn't really, I don't really think, if we say that all language needs to be a functional language, it doesn't have higher order functions, then, like, what is a functional language? Wait, did, I don't think anybody said that though did they no no but we were talking about like what you know you suggested rust as a functional language connor and i mean i think i think when you're talking in the world like systems languages or in the world of like imperative or multi-paradigm
Starting point is 00:18:40 languages when you talk about the functional style, that functional style of programming is related to, but I think quite distinct from, an actual functional language. You know, to me, like, one of the key aspects of functional programming is the immutability of values and all the nice properties that you get out of that. And I think that's sort of an essential component. And you look at a lot of what we call functional code
Starting point is 00:19:19 in something like C++ or Rust. And sure, there's a lot of examples where you do work with immutable values, but there's also a lot of examples where you're doing something that we might call functional style, but it's not actually that. Well, I think part of the problem
Starting point is 00:19:41 is that a lot of folks have different definitions. I think I've even heard you, Richard, at the top of a couple of your talks, like you will state your definition of like, I think you might even have a whole talk dedicated to it, like what you consider a functional people are going to disagree with this because if you take the superset of the list of criteria, whether that's pure functions, higher order functions, referential transparency, you know, first class functions, et cetera, and then, you know, there's 10 or 12 things. Every single person, when they talk about what is the functional programming paradigm, you know, what do you need in order to be functional? Like, you talk to a bunch of different academics and they're going to give you like, you know, 10 different subsets of that superset list. And I'm not sure if you off the top of your head, if you recall your enumeration, maybe we can chat about things that I've heard people say, like are required for functional programming or. Yeah. If you want to give that
Starting point is 00:20:40 list and then like after each one, whether you agree or disagree, like what makes it onto a's list well i mean like i said there's only two on my list which is avoid mutation avoid side effects but um so uh some of the things i have heard in the past so i i'm going to start with the first one that i think is the most ridiculous one that i've heard but i actually did see this it was it was on some link i read it was like hacker news or lobsters or reddit or something but somebody actually said you're not doing functional programming unless you have higher rank types which is the most ludicrous one i've heard because that is just to me is like absolutely zero percent to do with functional programming at all and i don't know what could make somebody say something like that is higher rank the same thing as a higher kind of types no they're they're
Starting point is 00:21:23 actually different um so higher rank is like a, for all in Haskell. Like you have to, okay. If you need to say for all to like, it's like, it's about like quantification of, I don't know.
Starting point is 00:21:33 That's a whole rabbit hole, but at any rate, we'll leave a link in the show notes for folks that want to read the Wikipedia entry. Yeah. Um, uh, but basically,
Starting point is 00:21:42 um, to say that like functional programming programming like you're not doing functional programming unless you have higher rank types is just like one of the most like ridiculous things i've heard in general uh but somebody actually made that claim uh and then defended it when i said this is this doesn't make any sense okay so so that's one um but the more common ones that i hear are like pattern matching people say like oh like this is like a functional thing like pattern matching i'm like i don't think it has anything to do with functional uh another is basically some types so this would be like algebraic data types or uh discriminated unions or tagged unions or like a lot of different languages call it different
Starting point is 00:22:18 things custom types in elm we call them tag unions and rock enums and in Rust. Basically, you know, you have a list of alternatives and those can have potentially data associated with them. Immutability in general, I've heard people say that like that's a functional thing, which I certainly agree that if you want to avoid mutation and side effects, then yes, immutability is a very helpful thing for avoiding mutation. But I don't think it's an exclusively functional thing. I certainly think any imperative language can have immutability. I don't think it makes a language functional or, you know, a style necessarily functional to have things that are immutable. I think it's more about like how pervasive they are and how, and to what degree you can sort of
Starting point is 00:22:58 stay in immutable land or versus like needing to split off into doing mutation directly. Those are the first ones that come to mind. I mean, I've also seen people, so the talk was called The Essence of Functional Programming, and it was a keynote at actually Functional Conf, which is in India, so I was remotely presenting at that one. But basically, I started with lexical closures. Are those necessary for functional programming? And I basically concluded after going through sort of the history of where lexical closures
Starting point is 00:23:28 come from that, no, it's not necessary for functional programming. But that's another one that I've heard people say. And one of the examples that I gave was Java before and after it got lambdas. And it's like, well, if you wanted to write code in a functional style in Java before and after lambdas, like what's the difference? And the end answer ends up being basically convenience because semantically lambdas are essentially syntax sugar
Starting point is 00:23:51 for what they had before. So it couldn't be the semantics because it's like the same thing as far as the VM is concerned. What were those called? Were they SAM classes? Single abstraction method? There's some acronym that I can't remember. It have been sam like yeah i don't remember i mean i i had to look it up for the talk
Starting point is 00:24:10 because i don't remember either it was like a runnable i think it was the interface maybe um i had like one method called run something like that yeah um a lot of the stuff you mentioned kind of makes me think of things that enable like what I in my head call like expression based programming versus like statement based programming. So it's kind of interesting that you said subtypes or just like ADTs in general, because those are very closely tied when I think about like what are the languages with great support for ADTs most of them are functional languages and then with ADTs typically becomes you know a sum type which comes with pattern matching which is usually quite nice and it's interesting to think like is that actually what makes it functional or is it really that that enables you to write like expression-based you know programs that return values instead of you know you in like the classic
Starting point is 00:25:12 anti-pattern that i really don't like seeing in languages like python and stuff is you initial initialize some variable outside a loop and then you modify it for every iteration of the loop and at the end of the day like what you have is some reduced value, which is the sum or the product or something, which really like you don't need any of that. You don't need to see any of that mutation. You just want to call a function called product or, you know, fold and then pass it the custom behavior that you need. And a lot of the sum types and stuff and pattern matching enables you to do more of that kind
Starting point is 00:25:42 of expression based programming. Do you have thoughts on like that? that's my initial reaction to what you just said or is i mean i honestly think that um some types of pattern matching could have just as easily come to imperative languages first because really i think they're they're mainly about conditionals like what's nice about them in my mind primarily is the exhaustiveness checking that's where you can say like, Hey, you've covered all the different possibilities here.
Starting point is 00:26:08 And I guess, I guess exhaustiveness plus the ability to say, I only have access to these pieces of data if this particular thing is set. So like the, the alternative being like, for example, a C union where you're like, okay,
Starting point is 00:26:22 I have one of these different shapes. And if you don't have a tag associated with that, then you're like okay i have one of these different shapes and if you don't have a tag associated with that then you're like well which shape do i have i have to figure that out somehow and that's a wildly unsafe thing so the nice thing about the pattern matching is you can say well if at runtime i have this particular tag then i have access to this data and otherwise i don't so it sort of removes a foot gun it's like well you know it makes the conditionals nicer but conditions are by no means a functional thing. Like every language has conditionals.
Starting point is 00:26:48 Every language has some sort of way to do branching or jump tables, whatever it's compiling to. And having some way to associate this data is only available when this particular flag is set at runtime just seems like a kind of a universally nice thing but historically it happened to come out of like the ml family of languages like robin milner's you know ml from 1970 something um and actually i don't know i don't know if that was in the first version maybe it was in standard ml which i think was 75 um but regardless like i think that could have just as easily been something that strausdrup with, you know, in like C with classes or C++. But it happened to, you know, come out of that group. Closures, I think, I think it's pretty hard to do functional programming ergonomically without lexical closures.
Starting point is 00:27:39 I mean, I'm sure it can be done in like if you really put your mind to it but i wouldn't enjoy it i don't think interesting yeah i mean while you were saying that i was thinking like the one of the number one uh i wouldn't call it like anti-patterns but problems that i have with python is like i i tend to write most of my code these days in like a functional style and i make use of the enum class which is python's best attempt at some types but they have no actually did they get matching maybe maybe i need to go look at that they might have gotten pattern matching in like 3 11 or 12 or 13 or something but like i i constantly have like a small function that just takes like a single enum as an argument and then i go if equals if equals if equals and then i exhaust it but then if you use rough or pylint or any one of the linters it'll be like you know
Starting point is 00:28:31 function does not have return statement at the end and i'm like well i know that i've exhausted all of them like i don't want to have to put like a return empty string or return none because then it's changing the type or you know changing the semantics of what i want and uh you know if there's a listener out there that knows there is like i don't know i'm not a python pro so maybe there is actually a a nice solution to this but yeah like you don't have that problem at all with with ruster or languages like i assume with rock either right be sure to check these show notes either in your podcast app or at adsb the podcast dot com for links to everything we mentioned in today's episode, as well as a link to the GitHub discussion where you can leave thoughts, comments, and questions. Thanks for listening.
Starting point is 00:29:10 We hope you enjoyed and have a great day. Low quality, high quantity. That is the tagline of our podcast. It's not the tagline. Our tagline is chaos with sprinkles of information.

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.