Richard Feldman

I have 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 uh Elm or like not necessarily Haskell, but uh 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 gonna have a bad time. Like it's just better to embrace the imperative and just use it like an imperative language.

Conor

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, 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 uh we're back with uh Richard Feldman. And now is the part I think that we are gonna transition to talk all about the rock programming language. That's ROC. 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 uh, you know, there's lots of programming languages coming out these days, but what's what's the goal that ROC is trying to achieve?

Richard Feldman

Sure. Um, so I guess I mean there's the story of like how ROC 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 uh you know uh is is interested in other languages, um, is that it is a fast, friendly, functional language. That's sort of our tagline. So fast in terms of performance, like runtime performance, um, not as fast as C, but uh but fast among sort of automatic memory managed language, um and also fast in terms of compile times, both of those are very important to us. Uh friendly in terms of user friendliness, so 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, uh code formatting, um, package management, all that good stuff. Uh so you don't need separate tools for all those different things. Um, and then functional because it is a single paradigm functional language. How's that?

Conor

So, my my first question is a fast, friendly functional programming language, the 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 uh Haskell and Scala and maybe Elixir, those are probably like the three most popular? You know, I'm probably gonna get flamed a little bit, 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 ROK on the slower end or more unfriendly end? Or how would you like juxtapose uh those languages versus rock?

Richard Feldman

Sure. So um 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. Uh without making a value judgment on, you know, like people's preferences. Um just if you look at like an error message that comes out of ROC's compiler versus GHC, it's just I think it's a lot friendlier, generally speaking, in terms of ROC. Now, I should say GHC is much more mature, so one possibility is that the error message you'll get out of ROC's compiler is there was a compiler crash, because we are not even at version 0.1 yet. Uh which which I don't think you should expect from GHC nearly as much. But assuming you do actually get a you know, a normal error that says like syntax error or type checking, you know, type mismatch or something like that, um we uh 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 ROC 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. Um but uh so uh comparing ROC to Scala, I would say that ROC should run faster than 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 ROC code bases yet. So uh and also we haven't realized all of our sort of ambitions with regards to performance optimization. We we've done a lot, but not as much as we plan to do. Um uh and then comparing to uh Elixir, I would say the main thing is that um it doesn't really have to do with being fast or friendly or functional. Well, but uh but rock, well I guess you could make the claim that rock is more strict about being functional than Elixir is, uh, 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 Scala's or Haskell's, 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 preferences, you might say one or the other 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 uh 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 on my podcast, uh Software Unscripted, several times um about uh types and elixir and stuff like that. So check out those episodes if you want to hear more about that specific conversation. But um I do think there's a significant difference between having a dynamic 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.

Conor

Interesting. Yeah, I definitely love, I mean, I think Haskell uh was pretty eye-opening the first time. Because I I think a lot of people conflate uh explicit types and like strong and static typing. Like they think, oh, if I have to have this really strong static type system, you know, I'm gonna 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 sprinkle them in to make sure you're doing things, you know, the way it is. But it's almost like a uh a fail-safe that if you if you don't want to write them, you can get away without writing them, which is which is uh pretty phenomenal. So I I guess my next question too is uh, 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 oh well, we'll link it in the show notes. I hope most of the listeners have seen it. There's a uh Rust developer interview YouTube video by this guy. I I can't remember if he's wearing like a blue wig, but it's a seven-minute interview about, or 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 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 his answer is this deadpan, like, well, what are you talking about? Go learn Haskell first. Um like it's like the first step to learning Rust is going and learning Haskell and then coming and learning Rust, and then you'll love Rust. Um, and so it's this this just general idea that Rust is a language that satisfies a lot of functional programmers and it's quite fast. So uh 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 most of the times, depending on exactly what you're doing. Or so like what would 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?

Richard Feldman

Uh well I think if you want a speedy functional language, uh a speedy functional language, a good reason to choose rock over Rust is that it is a functional language. Um I I I know I've heard people say that Rust is a functional language. I that doesn't make any sense to me. Uh Rust is like 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 hit I was used to. Um and I kind of assumed that A, Rust would just sort of magically take care of the performance for me. And in fairness, the parser seems to be plenty fast in this style. I have some plans to like rewrite it um using SIMD and some other things to make it a lot faster, but nevertheless, it's it's certainly not the bottleneck in our compilation pipeline, uh, which is already fast by a lot of languages standards, including Rust. But I have 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 uh Elm or like not necessarily Haskell, but uh 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 gonna 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 uh like to me, if you want to try to do the whole like I'm not gonna mutate anything, I'm not gonna do side effects thing, it works really nicely if you have a language that's designed around that, um, like rock or elm or Haskell. But uh, if you try to do that in Rust, I think it's it's just uh you're really swimming upstream.

Conor

Interesting. And it was in using parser combinators to implement your parser that that was the first time that you hit this wall.

Richard Feldman

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 do less of that. I can't I cannot compile your program because you're doing too many. And like in parser combinators, once you get past a certain size, like 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, but that sort of um took them out of the type system. This was not just about inlining, by the way. I tried like 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 um using there. And uh and so just turning the functions in like doing nothing else 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, uh-oh, like now what? I uh and uh I I 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. Um I thought I was gonna have to rewrite the whole thing until I found the macro trick. So now if you look at the parser 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, then 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 this style of programming.

Conor

Yeah, it's interesting actually that you say that because one of I 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 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 Lambda's enclosures in 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 uh your capture variables, it'll you know eliminate the trivial copyableness of your lambda. 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 uh 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 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. 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 and also too, uh 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 dyne 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, I'm like, yeah, okay. Well, I don't know how to fix that. But uh interesting. So your overall statement is that if you're looking for a functional language, it is a uh uh uh 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.

Richard Feldman

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 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 there's no reason they have to be. Like you can just have them in any language.

Bryce

Well what languages don't have higher order functions?

Richard Feldman

Today? C. Well, you can pass function pointers, does that count?

Bryce

I mean, I I I think for C there's definitely an argument that that uh at least it's not a not a properly supported um C I think is a slightly different story because in the C world increasingly you you have function objects and increasingly that has become the way that we pass um functions, and in fact, in some cases, even the way that we define um uh functions. Um but it but but uh I guess C and C is an example of a language that didn't have didn't have that like as built in from the ground up, but um can we can we think of a language that just really doesn't have them? That's hard.

Conor

Does zig? I don't know enough about zig.

Richard Feldman

Uh zig, I think is in the same boat as C, where if you if you would count C, then I think you would count zig, but like Zig doesn't have closures. So if you if you consider I mean now we're gonna get into definitional semantics. Like, do it, you know, do you want to count a function pointer as higher order? Do you want to count a function that is a conceptually of like something more than a function pointer but doesn't capture? Does that count? I've always thought of higher order function as 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, but 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 where sort of where do you draw the line question.

Bryce

But oh wait, so so so does Ruby not have so is Ruby's blocks the only way that it has to pass? It has another way to pass like a function.

Richard Feldman

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. Um that's just something I observed in my relatively limited experience doing Ruby code.

Bryce

So I guess like part of the question um uh is part of the definition that uh 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.

Richard Feldman

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 the mainstream.

Bryce

I think I think one of the problems is that um the mainstream imperative and uh multi-paradigm languages like C and Rust have uh essentially hijacked the meaning of functional. Um like the 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 if we say that that all language needs to be a functional language is have higher order functions, then like what is a functional language?

Conor

Wait, did I don't I don't think anybody said that though, did they?

Bryce

No, no, no, but but but we we were talking about like what you know you you suggested rest as a functional language, Connor. And I mean I think I think when you're talking in the world of like systems languages or in the world of like imperative or multi-paradigm languages, when you talk about the functional style. Um that that functional style of programming is related to, but I think quite distinct from an actual functional um language. Um to me, like one of the key aspects of functional programming is the um the the immutability of uh of of values um and all the nice property properties that you get out of that. Um I think that's sort of uh sort of an essential um uh component. And you look at a lot of what we call functional code in um uh in something like C or Rust, and sure there's a lot of examples where um uh 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.

Conor

Well, I think part of the problem is that a lot of folks have different definitions. I think I've even I've even heard you, um, 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 language, and you enumerate things and like you you even say at the top, like, you know, some people are gonna do disagree with this because if you take the superset of the list of criteria, whether that's peer functions, higher order functions, referential transparency, uh, you know, first class functions, etc. And then, you know, there's 10 or 12 things. Every single person, when they talk about what is uh the functional programming paradigm, you know, what what do you need in order to be functional? Like you talk to a bunch of different academics and they're gonna give you like you know 10 different uh subsets of that superset list. And uh I'm not sure if if you off the top of your head, if you recall your enumeration, and maybe we can chat about uh of things that I've heard people say like are are required for functional programming or yeah, if you want to give that list and then like uh after each one, whether you agree or disagree, like what makes it onto Richard's lists.

Richard Feldman

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 gonna 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, uh, 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 I don't know what could make somebody say something like that.

Conor

Is higher rank the same thing as uh higher kinded types?

Richard Feldman

No, they're they're actually different. Um so higher rank is like uh for all in Haskell. Like you have to Okay. If you need to say for all to like it's about like quantification of I don't know, that's a whole rabbit hole, but at any rate, the point is.

Conor

We'll leave a link in the show notes for folks that wanted to read the Wikipedia entry, yeah.

Richard Feldman

Um but basically um to say that like functional 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 I've heard in general. Um but somebody actually made that claim uh and then defended it when I said this is this doesn't make any sense. Okay. So that's one. 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. Another is uh 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 things, custom types in Elm, we call them tag unions and rock, enums and rust. Uh basically, you know, you have a list of alternatives and those can have potentially data associated with them. Uh immutability in general. I've heard uh people say that like that's uh a functional thing, which I I certainly agree that um if you want to avoid mutation and side effects, then yes, immutability is a very helpful thing for avoiding mutation. Uh 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 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 stay in immutable land, or versus like needing to split off into um doing mutation directly. Uh those are the first ones that come to mind. Um I I mean I've also seen people so in the the talk um was called The Essence of Functional Programming, and it was a keynote at actually functionalconf, um, which is in India, so I was remotely presenting at that one. But basically, uh I started with um lexical closures, are those necessary for functional programming? And I basically concluded after going through sort of the history of where lexical closures come from that no, it's not necessary for functional programming. But that's another one that you know 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 perform you know uh 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 for what they had before. So it couldn't be the semantics because it's it's just it's like the same thing as far as the you know the VM's concerned.

Conor

What were those called? Were they SAM classes? Single abstraction method. There's there's some acronym that I can't remember. Uh might have been SAM.

Richard Feldman

Yeah, I don't remember. I I mean I I I had to look it up for the talk because I don't remember either. Um it was like a runnable, I think was the interface maybe. Um had like one method called run, something like that.

Conor

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 uh 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 programs that return values instead of you know, you in like the classic anti-pattern that I really don't like seeing in languages like Python and stuff is you 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 and then pass it the custom behavior that you need. And a lot of the subtypes and stuff and pattern matching enables you to do more of that kind of expression-based programming. Anyways, do you have do you have thoughts on like that? That's my initial reaction to what you just said, or is it?

Richard Feldman

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. It's where you can say, like, hey, you've covered all the different possibilities here. Um and I guess I guess exhaustiveness plus the ability to say, um, I only have access to these pieces of data if this particular thing is set. So like the all the alternative being like, for example, a C union, where 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 conditionals are by no means a functional thing. Like every language has conditionals, every language has some sort of you know way to do branching and or or or jump tables, whatever it's compiling to. Um and uh 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, or maybe it was in standard ML, which I think it was '75. Um but regardless, like I think that could have just as easily been something that Straussdruck came up with, you know, in like C with Classes or C. But but it happened to, you know, come out of that group. Um closures, I think, I think it's pretty hard to do functional programming ergonomically without um lexical closures. I mean, I'm sure it it can be done, uh, in like if you really put your mind to it, but I wouldn't enjoy it, I don't think. Interesting.

Conor

Yeah, I mean, while you were saying that, I was thinking like then 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, 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 pro Python pro, so maybe there is actually a uh a nice solution to this. But yeah, like you don't have that problem at all with with Rust or or languages like I assume with rock either. Right. Be sure to check these show notes either in your podcast app or at adsphepodcast.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. We hope you enjoyed and have a great day.

Bryce

Low quality, that is the tagline of my podcast.

Conor

It's not the tagline. Our tagline is chaos with sprinkles of information.