You make a multimap that is your sort function, and then you call is sorted, and then you call sort, right? There are only three ideas there. And and the the the number of ideas in the problem is how I measure the triviality in a sense.
ConorIt feels like the things that I love about C and Haskell are just everything is there in BQN, like and more. Welcome to ADSP the podcast, episode 215, recorded on December 16th, 2024. My name is Connor, and today with my co-host Ben, we continue part two of our chat comparing different approaches to solving Aventive Code problems. In today's discussion, we talk about C, BQN, and more.
BenSo the question behind this observation is like, what is BQN for, in your opinion? What is it good at? What does it solve well? And what is its typical application?
ConorI mean, I think BQN is amazing for everything.
BenI understand you love array languages, but but I hope you can see where I'm coming from. Like it's not great for memoization. It's not great for when you have to put a big for loop and handle state in your code. It doesn't seem naturally suited to those things. It seems great for reducing data structures, summing them up, scanning them, doing all that stuff. It seems algorithmically, you can put all those things together really easily. Um, but when it comes to some of this other stuff, it it seems clunky.
ConorI don't know. I mean, I the more I will say that over the last year, like one of my goals this year was to one of my criticisms on my YouTube channel is that I just do one-liners. Which that's valid. That's valid. If you look at the corpus of code that I've written, the majority of it is leak code, these small challenges, because that's uh that's fun, and with a limited set of knowledge, you can actually just go pretty far. But one of my goals this year was to start to use BQN, not as just a toy problem-solving language, but as actual language for writing substantial pieces of software. And now I've written, you know, all of these programs are you know, hundred lines of code or less, but that's still a dense amount of BQN code. And so I've written a linter, a formatter, a unit tester all in BQN. And the language definitely has its limitations. There's no meta programming capabilities. And so I do some hacky things to get like a PyTest kind of unit testing framework. I'm doing some things that are, you know, don't feel great coming from languages that have these meta programming capabilities. That being said, it is it feels like the things that I love about C and Haskell are just everything is there in BQN, like and more. And the the way that I feel writing BQN code, it's it's just ever like it's painful almost to go to any language now in general. There are definitely things that other programming languages have that BQN doesn't, but I don't think it is a limitation of the array paradigm. It's just that BQN is not a language that has a metaprogramming facility. You could design an array language that has one, right? It doesn't exist right now. And it's in the back of your head thinking, you know, what are the small things that are missing that are made so much easier? And I think metaprogramming is probably like the biggest one, which is what led to your ability to do the decorator thing uh and memoizing functions in Python.
BenI mean, understand I am not trying to level particular criticism at BQN. I'm not trying to do down BQN or APL or any other array language. I think I am coming, have come to recognize, like like 40, 50 years ago, like if we wind the clock back, if we think about programming in the in the early 70s, mid-70s, even late 60s, in those days, there were hundreds of programming languages around, right? And they were many of them were not what we would think of today as general purpose. They were designed for certain application scenarios. You know, you wouldn't use a numerical language for string processing, and vice versa, for instance.
ConorRight?
BenYou had things like Fortran, you had Snowball, you had um uh you know, uh literally hundreds of languages back then, um, that many of which are now not in our consciousness anymore. Um you know, we could name the half a dozen that have really survived. But, you know, is the same actually true today? We don't tend to think that way today, but but you know, watching the things you do in BQN, thinking about what's easy to do in Python, what's easy in Haskell, what's easy in C ⁇ , um it seems to me like that might still be true today. Languages have our particular fit for application domains.
ConorOkay, yeah. So I I I get closer to I mean I still I still maintain that like I think I'm I can't remember when or where. On some recording somewhere, I said that, you know, BQN, it's been my favorite l language for a while, but the the gap that it is putting on other languages is like ever increasing. And a part of the reason, uh paradoxically, is because my discovery of like I always knew that there was a system while um loop, or like while higher order function technically is what it is. It's not really a loop. Um or I guess it's both. But I I never used to use that. And that meant that like for certain tasks it becomes difficult to do it in BQN without that kind of thing. But now that it's there, right, and I know I can reach for it, like there was uh um, I think one of the most recent problem that I did day 14, part B is like find a Christmas tree in your input. And I'm like, what? What do you mean find a Christmas tree? It's just like it's it's like a grid. They give you no description of what it looks like or how to find it. And I I made an educated guess and my educated guess, but basically you just need to iterate on these little things moving around on your grid for this rules of like how these things move. And then at a certain point, it just materializes a little picture of a tree. And uh that is like it's exactly what you need a while loop for. Like, you don't need a reduction or a scan, you need something that iterates conditionally. That's a while loop. Like, and uh and now that it's there, I find myself reaching for it probably more than I should, because that's one of the criticisms a couple folks have made of the videos is that sometimes there is an array solution that I ignore because the more intuitive thing to do, or at least for my brain, is to reach for this kind of conditional exit.
BenI mean it sounds like it sounds like what we're agreeing on is that uh, you know, when you start out learning a language, particularly one like BQN or like Haskell, the language and the paradigm are very closely knit, right? And and what you just said to me indicates that, you know, you're now sort of discovering Well, of course you knew, but but you know, you're doing different paradigms in BQN other than the array paradigm, right? Because the paradigm fits the problem better.
ConorYeah. And I think that's what I'm what I'm discovering is that there was this set of things that BQN is just lights out better than any other language, and I think that's probably what you're asking more. But what I'm realizing is that like not only is it amazing at that stuff, but it can still do the other stuff.
BenLet's talk about day five? The the print ordering ordering the pages for print. Day five. I think it was day five. Yes. Day five gives you page ordering rules, which are ordered pairs. Um, you get a bunch of pairs of pages where and the rule is that the first one must come before the second one. And that so that's part one of the input. And then the second part is a bunch of lists of page numbers, and you have to for the part A, you just have to say, are these numbers correctly ordered according to the rules? Right. Um so I look at this puzzle, and so I say I haven't solved this puzzle. I have not coded up the solution. But the way I would do it in C is to recognize that the first part of the input defines your sort function, right? And then you just say is sorted according to that function. The first part of the input is basically you would make a multimap or something, and and from that multimap you'd be able to answer the question, given any two numbers, are those numbers or are those numbers ordered? Is a less than b. And then you would just call is sorted. And I think part two, having watched your video, I've learned that part two you would call sort, right? According to the definition of the ordering, which is given by the first part of the input. So, which is odd to say that I think this problem in C is trivial or close to trivial. It's like it's like you make a multimap that is your sort function, and then you call is sorted, and then you call sort, right? There are only three ideas there. And and the the the number of ideas in the problem is how I measure the triviality in a sense.
ConorI would well, do I want to say disagree?
BenGo ahead. Disagree with me.
ConorMy guess is that whatever the C code ends up looking like is nothing compared to the elegance of what BQN gives you. And BQN actually uh has a like I I ended up once again looking at I I think I solved it initially, but then I ended up looking at other solutions. Like I always will try and solve it myself, and then I go and do like a code review because I think that's one of the best ways to learn is like sure, you you might have solved it your way, but seeing five or six other solutions from people that are experts in this language can lead you to some really nice um things that you end up learning.
BenSo I I do not doubt that the BQN solution is fewer characters by maybe one or two orders of magnitude, right? I'll give you two orders of magnitude on that one. But I'm questioning like the the especially in a world perhaps where um, you know, with an AI assistant in your editor, you can my question to you is is this changing um things at all? Right? If I could say to my editor, if the solution is like to me, I haven't solved I haven't coded up the problem, but we've just talked through a solution, and to code it up would be just a matter of hitting the keys in my head. And so, in a sense, it's as trivial to write that in C ⁇ as it is in BQN. Unless you can convince me otherwise.
ConorWell, actually, so let's take a step back because uh I'm not sure I fully have comprehended because I probably haven't actually I've been I've been too BQM brained. When you say your multimap is your comparator, what do you actually mean by that? Is uh do you have to I I assume you I assume you mean you do not mean I can pass my multimap as a binary operation. Like, do I I have to wrap this in a lambda to some extent, correct?
BenI need to write a less than function, right? Is a I need to answer the question, is A less than B? That's what will power the sort or is sorted. Absolutely. To answer is a less than b, I can look up a in my multi-map and see if see if b is one of its values, right? That or vice versa, right? Because actually the way the input is structured, so I check this. In the sample, seven numbers occur in the in the list of pages, right? Twenty-one pairs are given.
ConorYeah, it's complete.
BenSo seven choose two is forty-two, right? Seven ways to pick the first number, six ways to pick the remaining number. Half of forty-two is twenty-one, which is the number of rules we're given. That is sufficient to determine because if you're given obviously a rule one way round, it also applies the other way around for greater than. So this was the question I had when I read through this. Is the input defining an order that we can use to power sort? And in fact, I believe it is. Yes. Because so if I make that multi-map and I make it, let's say I make it sort of both ways round, or or my or my sort comparator looks both ways round, then I can that sort comparator is easy to write, right? I can I can answer the question is A less than B just by looking them up in the map in the map.
ConorOkay. I fully understand now. Yes. So it uh there's several different ways you could do this. One of them, which would probably be the one that I would lean towards, is just yeah, hand rolling a lambda after populating this multimap, and then yeah, ch doing a call into that and you're you're done. Which is, I agree, quite simple. I mean, I don't want to get into the rich hickey complex versus complex, etc. etc. But it's it's not a if you if you have in your tool belt the multi-map uh is sorted, the the algorithms, the data structures, and the mechanisms that are how to spell a lambda, it's a pretty trivial thing, like you said.
BenYou don't actually need to go Right The fact that we've just explained this in a couple of minutes, talking at the level of like, here's the algorithms, here's the structure, here's how you write the sort comparator, done. That indicates to me that this is simple, as you say.
ConorYeah, so I still would I still think that the BQN solution, not the one that I initially coded up, but the one that I ended up with, it is like a superpower level above the C one. And and I also think that well, and this is more of like a philosophical territory of like the the elegance and the number of keystrokes to spell like that solution in C ⁇ affects what does it affect? Obviously the elegance of the solution, but I think it also affects my ability to go and play with the solution. It's like I said, like I coded this up one way and then went and looked at other solutions and then immediately started like playing with my solution and modifying it. The experience of doing that, and that this isn't to pick on C ⁇ , this is to pick on lots of languages. It is it's almost like the it's it's that ability to play around with different solutions is non-existent. Like in a in a practical kind of like, oh, I'm I'm doing this with an is sorted or assorted, and now I gotta switch this out. I am just maybe I'm not a good enough C developer, but I constantly am like spending time just trying to get the thing to compile. Uh, versus like the malleb malleability. And I I think that's what's like there's other people that are do a way better job of articulating like the like it's like the freedom, it's it's similar to if you've read SICP and then you you get up to chapter five and you're building the circular evaluator interpreter or whatever the thing is called. Right? Metacircular evaluator interpreters. Metacircular, yes, thank you. Uh it's like you at that point you're like, wow, I just kind of wrote the whole language, like you feel like empowered.
BenYou can write Lisp in Lisp in like 25 lines or something. Yeah.
ConorAnd it gives you a sense of like, you know, what else can I do? Like I can now bend the I wrote the language, I can do whatever I want with it. I can go add my own constructs. And I feel like C fights you when you try to do this versus like array languages and in these simpler languages, like Lisp and Haskell, I shouldn't call them simpler, but just like the the fact that there's so much stuff that comes in the prelude with Haskell, and there's not namespace and and the the tax of double colons, and it's just like you can't that that one talk that I gave algorithms as a tool of thought, not notation as a tool of thought, was that like I can go and write 11 solutions in a grand total of like 30 characters uh to this one problem all equal. And I can't even like it's gonna take me a couple minutes just to get something compiling, and it's probably not gonna compile on the first time, is to say that like uh I think the C solution is elegant for what C ⁇ can do, but like it falls short of the power that you get with BQN. And and the BQN solution, we haven't even talked about it, uses this thing called like a table grade that when I saw it, I was like, oh my god, that is that's like mind-blowing. It's like these it's these things where you get the the uh uh uh inversion of uh indexes to get your histogram, or you get these table grades. It's like it's things that I don't I can't even begin to think like you would just you'd be hand rolling some bespoke thing in C ⁇ . Anyways, this is uh I feel like I'm picking on C ⁇ . Is that it's fine.
BenI can def I will defend it if you like. I mean there's something in what you say. I I accept that the there's something there, which is the terseness of BQN uh does help your ability to very quickly play around with it. Right. And like you say, build a dozen solutions, two dozen solutions in five, ten minutes and and try them all out. Um the question I have, which I am unable to answer at the moment, is like, does having an LLM assistant in your editor help with that in a language like C or Python? Does does it narrow the playing field there? I don't know. Does it narrow the playing field? That's a bit of a mixed metaphor. Does it narrow the gap, does it level the playing field at all?
ConorI definitely think it does, because whenever I program in Rust, that is essentially what I'm doing. I'm asking an LLM, use iter tools. I want to fold a map, destructure this tuple. I can explain it to it, and it goes bum, bum, bum, bum, boom. And where I typically would hit a you need an unsigned I-32 here, so you need an as, and you need a into iter instead of an iter here, all of those little bumps in the road, the LLM is gonna nail because it's studied a corpus of correct Rust code out there. And me programming in Rust with an LLM is like at least an order of magnitude, if not more, more productive. Right. But the code that I still end up with, I am still like it doesn't doesn't touch, doesn't touch the the nicety or yeah, the express the expressivity, the terseness, the just pure expression of the algorithmic ideas, I think. Yeah. And it's it's just like I never in any language other than BQN, maybe Haskell a little bit. I'll run into like I I I wrote enumerate for our all intents and purposes, uh, which is you know just zipping with your corresponding image. Yeah, it's called zip with index in Scala, and it's called enumerate in Python, and I think it's in C23 now.
BenYes, views enumerate, yeah.
ConorYeah, it's like four or five characters in BQN, and I'm at the point where like I s I saw it and I immediately read it, understood it, and was like, wow, this and it's tacit. It's a beautiful example of a tacit expression. Right. And it's half the length of the word, and like enumerate, maybe you can guess what that means if you've never seen it before, but like it's not a bad name, but is it a name that like I think zip with index, perfect name. You can if you know what zip is, uh you can get your way to what zip with index is.
BenBut enumerate I mean if you know what enumerate is, same argument. But yeah, but what you're saying, I think, is that you have cultivated algorithmic intuition, right? That is a big part of, you know, in the process of learning APL and BQN and the real languages in general, I think your ability to translate word problems into algorithms has grown tenfold, right? And and at some at some level, the problem of C is just that its affordances are so bad for that kind of thing. Yeah. Right? Everything in the standard is named after what it literally does, pretty much, not what you would typically want to use it for. Yeah. Yeah. Right? Like last time we talked, or when we talked to Sean, like the processal for me is always like rotate. Right? You look at rotate, and yeah, it rotates blocks of things. But you totally don't come away with the idea that the most common thing you're gonna want to do 99% of the time is rotate a block of one thing and reposition one element in your sequence, and that's a rotate. Like it's it it's named for the generalizate, the general thing it does, and not for the common use cases. And that's bridging that gap is cultivating algorithmic intuition.
ConorYeah.
BenAnd that gap doesn't really exist in BQN or an applicative language where you just uh you know put these things together.
ConorYeah, I think that's what you're nailing the Nailing the nail on the head? That doesn't sound like it's right, but anyways.
BenHitting the nail on the head. Hitting the nail. I was right.
ConorI was verb using both the nail as the verb and the noun. Hitting the nail on the head in that I don't know what the analogy is, if it's like, you know, colored belts in in uh you know jujitsu or it's some religious like ascension up the ranks, but like I started in like C algorithm land, then like elevated to Haskell where things were like cleaner and simpler, but like you know, this intuition you can build you know the exact same there. And then I discovered APL, and I feel like it's like the highest form of like if if you're on this like algorithm ascension path, right? Like you fall in love with this symbolic because uh and one of the talks that I'm planning on giving in 2025 is gonna be called something like paradigms within paradigms. And it's this realization that like inside of APL and BQN and these array languages is the you can so beautifully uh develop the algorithm intuition. But there are a number of other paradigms. Array programming, which is the rank polymorphism, like the elusion of having to spell map everything, map everywhere. But then there's also the combinator paradigm, which enables you to write this just absolutely beautiful tacit code, which it's it's so painful being in other languages, not and not just like you know tacit expressions like I want to use the S or the Sigma combinator, but like things like partial application. I was just looking at some of the Oh that is yeah, that's a game changer. Yeah, someone that liked a tweet that I had tweeted from a year or two ago in 2022. And I was it was me highlighting like, oh, if we got if we got both destructuring in Lambda parameter lists and something else, we could have code that looked like this. And the code still inside the body of the lambda was just doing return x equals equals one semicolon. And I'm like, how am I advocating for like this is where we want to end up? Like, that's just that's an that's a partial application of a binary operation. It's equals one. Like in Haskell, four characters. In most functional languages, not four characters, but it's still pretty short. Like, and it's so fundamental. It's so fundamental and so painful to do in a language like that. It's just they've got a partial application operator that on any binary operation, you just sit that in between whatever and the value you want to partially apply. And stuff like that, like so it's it's when I think you know, I get too excited about combinatory logic and like you know, the D2 combinator that's like, or I see the delta combinator. I was re-watching the CPP normal.
BenYeah, and I go, this is exciting.
ConorAnd then people think it's like this esoteric thing that is like, okay, how useful is that? But really, the useful stuff is the stuff that bores me because it's it's just happens all the time, but that's the stuff that's super, super useful. Um but then, anyways, back to the paradigms on top of paradigms is that uh there's two other ones, mask programming and then also under programming, where it's like these things that you don't even have access to in other languages.
BenYeah. Mask programming is something I've noticed you talk about. A lot of your solutions come down to that kind of thing where you make a mask. You mentioned that a lot in your in your in your code report videos. That is that is not a way that I typically think yet, we might say. Yeah. Um it is very much on the on the level of wholemeal programming, transforming data structures by by by making masks and using them to select out whatever and then you know reducing and re-expanding and all that sort of thing.
ConorAnd the the real moment when I realized that this is something that was like latent and I wasn't acknowledging as like a whole way of programming was when I was building the the formatter. And I was not making use of a use of a mask to split some stuff, and so I was kind of using like a filter thing. I was filtering out some unnecessary stuff. And for the simple case, that worked. But then when I wanted to do something more advanced that kind of relied on a couple criteria, that criteria depended on the structure of where things showed up. And after a single filter, you lose that structure. And so I needed a way to basically apply like multiple filter operations, but like like structure-preserving filters. And that's what a mask is. It it's just booleans, ones and zeros. And so as soon as I switched from splitting using like filtering, like in functional languages, to splitting using masks, I can just stack those masks on top of each other, do a column-wise reduction, and then poof, now I have my final mask that I want. And like how to solve that in a kind of functional style where you're just making use of the filter, it's like it kind of falls apart because now instead of filtering, I'm doing something with a scan and multiple scans, and a scan's not really what I want. I just want to do this kind of mask-based splitting or mask-based filtering.
BenAnd and yeah, anyways, I uh it's having more tools in the toolbox, right? And sometimes having an entirely new toolbox you didn't know existed.
ConorBe sure to check these show notes either in your podcast app or at adspthepodcast.com for links to anything we mentioned in today's episode, as well as a link to a GitHub discussion where you can leave thoughts, comments, and questions. Thanks for listening. We hope you enjoyed and have a great day. I am the anti brace. Um