Conor

I put it to Bryce and I said, Bryce, do you know why it's so special?

Bryce

I think I got it pretty quickly, actually.

Conor

Oh man. So now we're gonna do there, where he said, I'm pretty sure I got it pretty quickly. And then I'm gonna go and go back to what it was because I'm pretty sure I'm pretty sure it took a little bit of hand holding. Welcome to ABSP the podcast, episode 54, recorded on November 23rd, 2021. My name is Connor, and today with my co-host Bright, we program Partition Live in BQN and then talk about why it's so awesome. Alrighty, so now uh we might have missed it.

Bryce

See the good the good thing, Connor, though, is that you've chosen to solve this problem in this this is BQN?

Conor

Yeah, BQN.

Bryce

In BQN, which means that yes, last time you got you know, we lost this great, exciting discovery of me um, you know, sort of figuring out um like as you were showing me how like the language worked and like how to solve this problem, but it's so esoteric that you were gonna get to rediscover that all over again.

Conor

We'll see. I'll actually be interested to be to compare how quickly you pick up what you learned and potentially completely forgot last time. So what we're doing for the next uh hopefully less than 30 minutes is we are we're look taking a look at this partition problem that we explained in last episode. And we are going to be moving, I believe it's the odd numbers to the front and the even numbers to the back, which Bryce uh astutely pointed out is just a single algorithm in C, which is stdd partition. And we're gonna solve that in an array language. Uh so the first thing that we need to do, uh, which, you know, so like as we said, Bryce have done this before, but we're still gonna go at the same, try to go at the same speed as we did last time, is we want to create two different masks, aka Boolean masks, and a Boolean and an array language is a one or a zero that correspond to the even elements and to the odd elements. Um so if you do modulus two, this pipe is the modulus operator, you then all right, and then BQN, so I guess I should explain. Uh APL is the OG array language. BQN is the newest array language on the block. It's uh if you take the A and add one, you get B. If you take the P and add one, you get Q. And if you take the L and add two, you get N. And that's because Marshall messed up when he was rotating the letters by one.

Bryce

Wait, no, is that really the story? Yeah, and uh and then when he's gotta be kidding, the name of the language is on off by one error.

Conor

Yeah, yeah. And and uh there was a backronym that um I think is sort of big question notation that you know it's a you know APL and started as a notation for mathematics, and BQN is the next evolution of it that is used for answering big questions. And that once the backronym had been set, then it was like, oh, it's too late to go and fix it. And BQN does sound a little bit better than BQM. Uh, and so we just he he just kept it like that.

Bryce

Yes. I'm not gonna say what BQM sounds like.

Conor

BQN?

Bryce

No, B B BQM, I don't know, sounds like sounds like something dirty. BQN, though, I feel like that's fine, but BQM, I don't know.

Conor

Get your head out of the gutter, man. It also has it can be pronounced uh APL sort of has an alternate pronunciation um as Apple, and BQN has an alternate pronunciation of bacon. Um we've uh so I was I'm screen sharing with Bryce right now, so we'll talk you through this. The first thing that I did was two pipe, pipe being the modulus operator, and then one, two, three, four.

Bryce

Is there a way to record? Ah, fine, forget it. All right, all right, all right.

Conor

You'll just I will I will I will release a YouTube video this Friday, which or maybe next Friday.

Bryce

You said you said that after the last time we recorded this video.

Conor

Then we lost we lost all the stuff. We lost all the footage. So um it's supposed to go out the day that this also goes out. Um but APL uh in order to create an array, you just put uh space separated numbers. Um but in BQN um you don't have that. You have to put this little sort of smiley face underbar um to create an array. So if you do two modulus, one, two, three, four, you get one zero, uh one zero, where the one is corresponding to the odd numbers. Now, in order to um filter these basically out and just sort of copy um the uh odd elements, you want to use a fork. So let's explain this. This is an S prime combinator, we explained this last time, um, where you have three functions where uh it's three functions in parentheses, a, b, and c. A and c are unary functions, and b is a binary function where the evaluation, and so the argument is x here, uh, the evaluation is b of a of x, comma c of x. So basically you pass your argument x to a and c evaluate those and then pass the results of those to your binary function b. Um so our first uh unary function here is gonna be the composition or the binding of two to the modulus operator. So modulus is a binary function, but if we uh compose this, I think that's the right one, um we are going to get our one, zero, and I need to comment out these little explanation things here. Um and then B and C, for C, we want the identity function. And then in between the identity function and our partially applied uh two modulus, we want to put basically filter.

Bryce

And if we get this, this does not work because I think it's so the idea is that uh uh filter is a function that takes two sequences, one that is you know this um Boolean mask. Boo Boolean mask um uh as the first argument, um, and then the second sequence is the elements, and then for e for each element in the in the Boolean mass sequence that's true, it keeps the corresponding element from the second uh sequence.

Conor

Correct. Exactly, yeah. Um and so initially my partially applied to modulus wasn't working because I was using a composition operator called a top, and I needed to use one called before to partially apply. Um but that's a detail.

Bryce

And so th thinking about this in C terms, I think the the equivalent would be something like a um I I was gonna say I was gonna say a copy if, but I think that actually that's that's not the um the best way to mentally model this. Um I think probably the way and I say that because copy if is like is like one of these algorithms that's like it's already a composition of two different like things of like copying and and the filtering. Like it it does you could do this with the copy if, but I think it's probably something like a copy with a with some range piped in. I don't know, what do you think?

Conor

I think I think copy if is exactly what this is. Yes, technically it's all bundled into one algorithm, but like what we're doing here, the creation of a Boolean mask that is being used to copy elements is not actually possible with C standard algorithms.

Bryce

No, no, but with like a transfer like that that's what I'm thinking. You know how all of the um all of like the range algorithms they have that uh projection? Yeah, the projection. Yeah.

Conor

So the thing is what the projection will do is it will modify your sequence before you copy it, um, which is not exactly what we want. However, there is an algorithm that exists where you can provide it. I won't give you the name of it because it's a dead giveaway if I do that, but you can provide it with an alternate uh sequence that represents the Booleans that are exactly what we're doing here. So you basically have a copy if algorithm that you provided a range that you're going to be conditionally copying elements, but instead of using that sequence with a predicate to determine uh do I want to copy these, you provide it a secondary sequence and a predicate, and you based on the evaluation of that unary predicate on the alternate sequence, you copy from the original range. And so that alternate sequence has a name. Um and I that'll be the hint that I give you if I tell you that, you'll probably instantly know the algorithm. Um although the algorithm is copy F. There's another huge hint. It's just not the C standard uh uh copyf. Oh, wait, this isn't a C standard algorithm that you're it is a C algorithm, just not a standard algorithm. It's not in the algorithm header.

Bryce

Oh, it's a numeric algorithm?

Conor

It's not that would also be a standard algorithm.

Bryce

Uh okay, it's not in the standard library.

Conor

It's not provided by the standard library.

Bryce

Is it provided by Thrust?

Conor

It is provided by Thrust.

Bryce

This podcast is just like it's just constantly Connor teaching me stuff about the library that I'm supposed to be in charge of. Although it's not actually true. It's the these days there's a the the there's another person on my team, Allison, who's taken over uh uh uh maintaining um thrust, so I'm no longer the glorious leader of thrust. Um which is great. Like, you know, just you should always just delegate your job off to other people. Um but uh but that's a that that does not mean that I should not know these things.

Conor

Um Yeah, so when I discovered the stencil overload of copy of, it is an ex such an extremely useful algorithm because there are so many situations where exactly what that overload provides you with is what you want to do. You want to conditionally copy something based on some other sequence, um, and so that alternate sequence is called a stencil.

Bryce

Yeah.

Conor

Which is actually not the best name because it's sort of close to like a tile, and that's something completely different. Yeah. But uh the stencil overloads, and they exist for a ton of algorithms in thrust, basically, um, and sometimes you actually so you in in the C standard algorithms, you usually only end up with we'll call them like sets of one or two iterators. So like one that'll provide you the input range and potentially one that provides you the output range. But in thrust algorithms, you can end up with three sets, and I say sets because a lot of these algorithms, as soon as you specify two iterators, begin and end for one range. You only need to specify the first one for the other two ranges.

Bryce

Right, because because the the it's gonna assume that the size of all of the other ranges is the same as the size of the first or something like that. Or or where there's other assumptions about the size, you know, that it's um for something like a copy if, you know, you don't know what the size, you don't know how many times you're gonna write to the output, um, but you know that it's gonna be less than n, for example.

Conor

So here's uh and so this this algorithm is perfect. So you're gonna end up copy if your original sequence is the one, two, three, four. Your secondary sequence would be uh, I would do it with a thrust make transform iterator. That's basically doing uh what we're doing here, the two modulus, which is then gonna sort of on demand generate you your your ones and zeros or trues and false.

Bryce

So the question is what's the unary predicate that you are going to pass as the final argument to this that'll be applied to your um and technically wait, wait, so okay, so you have the copy if with the stencil, and then the the first the uh one of them is this this transform iterator um uh sequence, that makes sense. And then you're asking what is what what are you asking?

Conor

So there's and there's a final unary predicate that is applied to your stencil.

Bryce

Why?

Conor

Well, so this is sort of as I'm asking this, I realize the question does the identity. Exactly. So thrust identity is what you would use, which is what so that's the thing is trying to model what we're doing here in BQN, that copy if pass the sequence one, two, three, four, past the transform iterative that does the two modulus and thrust identity, that's what maps to this. Because the transform that does the two modulus is the two uh composed with pipe. Um the the filter is our copy if, and identity is the thrust identity. However, that being said, in C ⁇ you wouldn't really do that. You don't actually need the stencil overload. You would just use the overload where the unary predicate is doing the two modulus.

Bryce

Yeah, but I I I was just talking about like like what's the what is the most true way to model what you're doing here in C. But the the the the the identity function here is different than the identity function in the copy if because the identity function in the copy if you're saying that in the cot this the thrust stenciled copy if it applies this function to this the the stencil sequence. And this in some ways it sort of reminds me of the by-key algorithms. I actually it's that seems a little silly to me. It seems like we should have a copy if stenciled overload that does not take um uh a predicate function where it just assumes that the um uh that the the you know the the the mask sequence is convertible to bool um and just like you know uh just assumes that and tries it. But I mean like you know, it's that's that's that's not a super common use case. So like I get it.

Conor

I mean it comes up often. I mean, probably half the times I've used the copy of stencil overload, it's with thrust identity as the unary predicate. Although my next thought was like, we'll just make it the default, but then I remembered I don't really like default arguments. So yeah, probably I would prefer just the overload. Yeah.

Bryce

But uh we should we should move on. We should move on.

Conor

All right, yeah, we got we got 20 minutes left here. So we've got our ones and zeros, uh, and then now we've used it with a S prime combinator that you know the binary operation is the filter or compress. And then we're passing the mask and identity, which gives us basically one, zero, one, zero uh with the binary operation filter in between, and then one, two, three, four, and it leaves you with one and three. So in order to get our odd numbers at the front and our even numbers at the back, we basically just want to copy this little fork here and put them next to each other. And so I think I asked you last time, what do you think this evaluates to where, and it's actually not comma, it is this little sideways, uh backwards s looking like thing. So this is catenate. This joins two lists together.

Bryce

So what do you think both of these this will just give you one, three, one, three, right?

Conor

Correct, yeah. So if you copy this and catenate the result of each of them, you just get one, three, one, three. So what we want to do basically is just in this uh second fork that is just currently equal to our first fork on the left, we want to basically change our ones to zeros and zeros to ones. And there's a function in BQN and an APL called not. And if we I think if we just put this in parentheses and evaluate this, we should get that. Um so now we have odd numbers at the front, even numbers at the back. I can't even remember what the problem originally stated, but you know, it doesn't really matter.

Bryce

I think it might have been the other way around. Um and yeah, but I don't think it really matters. It's it's simple enough to change it there. But I seemed while while I was pretty happy with this the first time around, I seem to recall that you were, yeah, you gotta put the the pipe around. Come on, Connor. Even I, even I could see that was gonna be a problem.

Conor

So yeah, you were happy at this point, and I think you had even remarked, maybe we'll go find, we'll go find previous Bryce, because we do have his audio, and he says, Ah, this actually it looks noisy, but it it actually does make sense. Yeah.

Bryce

Um but you were like, you were like too many, too many parentheses.

Conor

Correct. Yeah, when I look at this solution, there's one, two, three, uh, four times two is eight different parentheses. And more important, so like this many parentheses tells me that like this is not the ideal APL or BQN solution. But on top of that, we have so much duplication here. We basically have you know the two composed with modulus, uh slash identity. We have that twice. And then the only thing that's different between the two is that we're negating um the two composed with modulus. So uh what can we do to make this better? So let us see if we can sort of extract out the identity and the replicate or the compress and two compose and uh go from there. So I can't actually remember how I did this in the previous video. Um but the first thing that we're gonna try and do, so we're gonna sort of do this. Uh I I had prepared last time to make sure I wasn't sort of meandering through this, but now we're gonna meander through this. So we're gonna try and um so let's see if we can. We need to put this in parentheses. So we've sort of factored out the two composed to the left and then the identity to the right. And then for our binary function in between these two things, we're just catening for the moment. So what we have here is one, zero, one, zero, and then one, two, three, four, all in the same array. And so now we can turn this inner fork, which is currently not a fork. So if we add an A and a C here, what we want to do is basically we want to basically turn each of these, I believe, into forks. So let's just try and do that simply. Yeah, this this is this is and we'll use identity and so actually, and this is where the explanation of a binary fork comes in. So previously I explained when you have a fork, it's two unary functions with a binary function sandwiched in between. Um, however, which is what on the screen is sort of being described here, you can also have, and we'll call this x, y, and z, and then we'll call the input n here. You can have a binary version of this where so and it's actually, we'll call this a and b. So binary functions are infix and unary functions are prefix. So that's why the x is on the right here, but the a and b sandwich, the a and b are our arguments, and x and z are our binary functions that are going to be used first, and y is our binary function that's gonna be used last. This evaluates to the following. So we have y of x of a, comma b, and then z of a comma b and parentheses. So we're evaluating x and z, passing a and b as arguments to both of those, and then the results of those are being passed to our binary function y. And so what we've done here for the moment is we've just used uh what was identity before, but in a binary context, the left and right tax, which is basically look like sideways T's, one's pointing to the right, one's pointing to the left. These are binary functions that just one takes the left argument, one takes the right argument. So if we do this, and I have the right number.

Bryce

You have a stray C at the end there.

Conor

Oh, right, yeah. Uh and is this I think we don't need that parentheses. And we need to comment out explanation. So at the moment here we've got one, three, one, three, because we don't have not being used anywhere. So basically the inner fork is just replicate with left and right as on either side of it for the two outer binary functions, and then catenate in the middle. But if we compose with this identity the not function, and it's gonna be one of these, basically that knot is gonna change the mask from one zero one zero to zero one zero one, and we're good to go. However, this looks pretty messy. We still have, I believe.

Bryce

Yeah, still equal number of parentheses. One viewer, maybe? Nah.

Conor

No, I think yeah, one, two, three, four, five, six, seven, eight. However, we can we can clean this up. So whenever you have a um a basically a binary fork where the left binary function is left and the right binary function is right, this is the exact same thing as just the middle binary function. Um, because that's what a binary function does. It takes two arguments, uses the left one on the left and the right one on the right, and you're good to go. So this will be the same thing. And then once you have gotten rid of the fork and you just have a binary function, well, we don't need these parentheses. Um so now we're down to just basically we reduced, what was that, four characters. Uh all of it was just noise. So absolutely beautiful.

Bryce

And I I'd also argue that it it um you know it it ha I I think in practice probably the BQN compiler optimizes this under the hood, but it it it it um it looks more efficient because you're only doing the um you're only building that mask once, right? That you've lit like previously you had two places where you built the same mask, and here you've lifted that out.

Conor

So yeah. Thing to note though, BQN does actually not a compiled language. Most array languages are interpreted.

Bryce

So whatever. I in compiler interpreter the thing does will do that optimization, I'll assume.

Conor

And uh we're pretty close to being done this solution, but there was one last step that I showed last time where because so right now we've got our two compose modulus as our left outer uh fork function, and then identity, that's what we started with. And then on the inside we've Got sort of basically a fork where on the right it's just uh replicate or compress. And then the binary function in between that one and the other one is catenate. So you're gonna end up with sort of one three as the result of everything catenated with at the beginning, the result of basically our uh knotted or negate or not, yeah, knotted. So you're the complement of one zero one zero, which is gonna be zero one zero one. And like I said, this is all probably lost all the listeners, but there will be a YouTube video for those that are intrigued enough by this discussion to go and watch it and it'll make sense probably more with visual cues. But we can switch um the left and the right sort of binary functions in this inner fork so that it looks like this. And because the way that APL evaluates is uh right to left, we can now get rid of the two parentheses that are sort of containing this.

Bryce

Now, but I'm I'm gonna argue, and as I argued last time, that the parentheses actually added some clarity um to the uninitiated. Yes, but but like now you're relying on people know knowing uh um how uh precedence works in this uh language. Like now there's just a soup of symbols in the middle of this. And there's no there's no hierarchy or structure. I think it was.

Conor

This is true. I mean, I prefer this because I have I have dived into the pool of knowledge that is the array world, and um and also too, we haven't even fully like so we've swapped the order, but now the odd numbers are at the front. And so this is where I think I got all excited last time and was like, you know, and this is where uh the C combinator comes in, aka flip from Haskell, aka uh the C combinator, and that's this uh little squiggly superscript symbol here, and that basically reverses the order of the arguments passed to your binary function here being catenate, so we get back this. And that was even at that point, I think Bryce was like, okay, no, no, no, let's uh let's rewind.

Bryce

I think you you took out two parentheses and added a C combinator. And I do not think that increased clarity.

Conor

We can agree to disagree, although many people would just agree with you. Um and then yeah, we can name this, and once we name it as well, uh we can also get rid of a couple extra parentheses. But you could have done that with the previous solution as well. Um, we don't need the arrow here. And then boom, that's that's what's up. Um so pretty freaking awesome. And uh I yeah, I love, I just love, you know, what was it? Just this past Sunday, the leak code contest um happened, and then I was solving uh the solutions in APL and BQN, and it was it's just marvelous. I ended up using a psi combinator inside the S prime combinator, aka a fork, in order to apply a unary function twice to the result of two modified things. That didn't make any sense, but it's just the power um of APL and BQN and the and the operators that you get with it. So we're done pretty good. We got uh you know 10 minutes left before we gotta drop off. Um and at this point Yeah, yeah.

Bryce

I was about to say, at this point, you got very excited.

Conor

And yes, at this point I got very excited. Well, this is this is where we're probably I'm gonna ask this question and you can answer it now, but I'll probably go and find your initial sort of meandering because you know the answer now. And so at this point in the previous recording, which all of my audio got lost, I was like, and so we just did this super fun walkthrough of an APL slash BQN solution to this Leico problem, which was super fun, but it was not the point at all. The walkthrough was just extra, you know, icing whatever on the cake. The real question, and like the whole buildup to get to this point, is what is special about this solution? Because a lot of people, their initial remark is gonna be well, you know, you're creating a bunch of temporary arrays in the form of masks and you're doing all this extra work, whereas if you just compare it to the implementation of like the two-point the two-finger uh partition solution where you know you work from the front of the array and the back of the array, and you find two that uh should are in the wrong partitions and then you swap them, like from a serial or consecutive point of view, that is much more efficient than all this temporary array uh creating and copying. Um and so I understand that point of view, but I think this solution is super, super, super awesome, sort of in light of that, and why people shouldn't be discounting like this way of thinking in array languages. And at that point, I sort of I put it to Bryce and I said, Bryce, do you know why it's so special?

Bryce

I think I got it pretty quickly, actually, which is the answer is that this can be paralyzed.

Conor

Oh man. So now we're gonna do there, where he said, I'm pretty sure I've got it pretty quickly. And then I'm gonna go and go back to what it was, because I'm pretty sure I'm pretty sure it took a little bit of hand holding. Because you I can't remember what you said. You said a couple things, and I was like, uh that's a good point. That's a good point. I didn't think about that. Not what I'm looking for.

Bryce

Oh, um yeah, yeah, yeah, yeah. Potentially inefficient because you might be generating those Boolean masks twice. Um, yeah. Is that you you can you can it doesn't have to be inefficient because you can you can do the fusion here if if you have this point-free expression, then you can go and optimize it so that you only do a single pass. Yeah, this can be paralyzed, can't it? I I I I think I got there pretty quickly. And I think my other points were pretty pretty reasonable points.

Conor

But but then you pointed out that this is actually That's the important part, is that I think I said yes, it can be parallelized, but even even more exciting than it just being parallelizable. Um like take it a step further. Like, what what am I what am I actually really excited about? And then what did I say to that?

Bryce

This is the current algorithm uh that thrust uses to implement its parallel partition.

Conor

Correct. And so that was and that was what uh after having done this the first time I did this a couple months ago, uh, when I was you know just messing around, I th I thought, I wonder how thrust partition actually does this. And then when I went to the thrust partition docs, very quickly you could see it's not identical to what's happening here, but you're basically you're creating some temporary storage, using it to uh I mean, we will follow up with this in a follow-up episode, which is what Bryce was referring to earlier, that there's a count if that happens, a thrust count if to figure out how many elements are gonna be in the first partition, so you know where to start the second partition. What do you want to say?

Bryce

Well, I was I was gonna say that that perhaps more specifically that that the way that Thrust implements this today is there's three passes, I believe. There's a count if to figure out you know the size of each of the partitions, and then there's two copy ifs. Um uh, you know, one for the trues cases and one for the false cases. Um and uh and during our discussions last time, I think I came to the conclusion that and I should add a caveat here. Um uh and the caveat is for basically all of the stable algorithms in Thrust for stable sort and stable partition, um, the same algorithm is used for the stable version and the unstable version. Um just because uh that that the there has not been a benefit in doing something different. And I think in the case of sort, there's not really a an option that would like a parallel sort option that would be unstable. Um uh now interestingly, we once we started talking about this repetition, we realized that perhaps um perhaps we could do an unstable partition faster, and then I think we I think we came to the conclusion that we might even be able to do the stable partition faster. But basically we we we quickly came to the conclusion we probably don't need to do that count-if um so we can get rid of that pass. Um and then we just have those two copy ifs. Um and the and the way that you could avoid doing that that count-if um is that if you if if the copy ifs instead of them both starting at the front of the sequence, if you have a type of iterator where you can get a reverse iterator, then one copy if starts at the front, one starts at the back, and you don't need to figure out where the second copy if you know is gonna need to begin. Um and uh and you just have the potential you know stability problems. Um but then the other thing that sort of occurred to me is, you know, well, if that's the case, couldn't we just do it in one, you know, in one pass? Um uh uh and I think that might be I think that might be possible. And I think that's what we're gonna explore in some future some future episode.

Conor

Yeah, I definitely think the the two pass one, it's it's it should be more than possible. The one, I'll have to see if what you're thinking.

Bryce

One pass with Owen you can keep in mind here, we have an assumption of ON temporary storage. So we don't have to do it all in place. Um the algorithm today already uses so the algorithm today is three passes and uses ON temporary storage. Um like uh apparently not a lot of people are doing uh parallel partitions because like the current algorithm is slow. Um so it was clearly never a big priority for us to uh optimize it. But if you are doing a parallel partition, um we're gonna make it faster. We're gonna make it faster.

Conor

Although, you know, slow on a GPU sometimes can still be a lot a lot faster than whatever you're doing.

Bryce

The the the case of um uh the there's one reason that the the um the one pass version that I have in mind might be slower than the two pass version. Um uh and it's it's it sort of gets into our efficiency versus speed episode from a while back. Um that one pass algorithm might be branchy. Um uh and so if you if you have to do a single pass on a GPU where that single pass has an if statement, sometimes that's gonna be uh slower than doing two passes where there's no if statement um uh in each of the passes. But we'll see. We're gonna explore all that. It's gonna be awesome. Yeah.

Conor

Live. We're gonna start a whole series on uh refactoring and uh increasing the perf of thrust algorithms. And the a great note to finish on, which I just realized as Bryce was explaining, you know, how the thrust partition algorithm works and how it could be um you know enhanced or improved, the vocabulary that we're using here, like this is why this is why you should learn your algorithms. Going back to the whole C seasoning, you know, Sean Parent, you know, know your algorithms, and then a bunch of people popularizing that sentiment, is the vocabulary that we're using to talk about doing this is algorithms. We're not talking about for loops or or you know kernels or all this stuff, the the lower level pieces that we could build this in. We're talking about a count if a couple copy ifs, we're talking about a transform iterator, which is just sort of a you know the equivalent of a transform algorithm, um, but that you know just sort of does it on demand or lazily. And in my opinion, your ability to like we are communicating at a higher level than is possible if we don't have the names of these operations. Uh which I just think like there's a lot of folks that, you know, you'll give a talk on algorithms and then they'll come up afterwards and they'll say, ah, is it really worth it? You know, it's a bunch of effort to go and learn, you know, all the different, you know, names of these algorithms in every different library. And that's the thing, is like the standard algorithms, which is sure, these are in thrust, but they're all equivalents of what exists in these standard algorithms. It's not something that you like, you have to go learn n different times. You learn it once, and it's now enhancing the vocabulary with which you can communicate with your coworkers. Which I I don't know how this is.

Bryce

It's it's that um the the algorithms on them s on their own aren't particularly useful. The the interesting thing is the ways in which you can compose and decompose them together and combine them, and the way in which you can write your own algorithms just by plugging the right operators into the existing algorithms. Um like, you know, like reduce, if reduced like only added up integers, like that's not a very exciting algorithm. Um but think about, you know, think about all of the things we've done on this podcast with, you know, the the notion of a reduction. Um uh and like underst understanding that pattern, that algorithmic structure, um is in and understanding how you can use it and combine it with other algorithm algorithmic structures is really powerful.

Conor

Yeah, I completely agree. And on that note, we'll we'll wrap it there, 'cause that's such a good a good note to end it, and we have to go in four minutes. So thanks for listening. We hope you enjoyed and have a great day.