Conor

Continuous lifelong learning though.

Bryce

Continuous lifelong learning is a fancy way of saying we don't actually know.

Conor

Welcome to ADSP the podcast episode 23 recorded on April 17th, 2021. My name is Connor, and today with my co-host Bryce, we talk about anamorphic algorithms and much more. We got a bunch of reactions on Twitter. Um, but then one of them was Tristan Brindles saying he didn't know what segmented algorithms did, um, which we explained two videos ago, or two uh two videos, two uh podcast episodes ago.

Bryce

And so there we could we could go into an explanation of You mean like the the by-key algorithms?

Conor

Yeah, so we could try and restate that just because I think it's important, but also too, like within that there's the class of algorithms called anamorphic algorithms, which are the opposite of catamorphic algorithms, so like uh reductions aka folds or the opposite of reductions aka unfolds, like split group by.

Bryce

Um, but we can save that for another day and we can just sort of chat about so I I I think the best intuitive way to understand um uh the by-key algorithms is to think about them like they're batched operations. That what I really want to do is a bunch of like I want to do reductions on a bunch of different things, um uh, but um I want to batch it into one pass. Um and and that's that's sort of my mental model for Baiky. Now I've uh I've gotten into plenty of debates with people over this, um whether whether this is a good mental model, because batched APIs may look a little bit differently, but um uh like like truly batched APIs would be a bit different. Like, you know, you'd you'd be able to have different data. But I sort of like I think the best way to think about it is that like you've got one set of input data and you've got some operations that you want to do um uh on parts of that input data. Um uh but instead of doing each one of those operations separately, you you can construct um a single, you know, a single operation that will operate on those segments um uh uh in in one in one fell swoop.

Conor

That makes sense to me, but I could see why some people would disagree that that's a mental model you want to use. Yeah, so I guess what do I wanna what do I wanna say? So I guess we are gonna talk about this then. Um So the first thing, like the way that I learned about bi-key algorithms was by translating Haskell code into sort of a C thrust solution.

Bryce

And I'm almost certain that I told you about bi-key algorithms like right after we met, and that was how you first heard about bi-key algorithms.

Conor

Yeah, yeah. So I I think I mentioned this in two two episodes ago was that I gave my talk, and then after that talk, you said, Oh, you could probably just use a by-key for that one solution uh that I had written in Haskell.

Bryce

And that that that listeners was back when I was clever.

Conor

And that was 100% correct. And and this actually, so it got asked by so Tristan Brindles. Uh we'll put a uh link in the show notes, um, and actually I'll just get the tweet and forward it to Bryce here so he can stare at it while um we're chatting about this. Copy link and we paste it to Bryce.

Bryce

Exciting podcast content on on Twitter.

Conor

So uh so yeah, Tristan um he responded to one of the ADSP tweets and said uh just listen to episode 20, really, really enjoying them so far. Could you use this approach to solve the maximum consecutive ones problem?

Bryce

Uh that's the third problem, right? The one that we actually we actually record after that episode, I went off for three hours and tried to solve it in one pass, and then I thought I had it, and so I made you get back on for us to record another hour of content where I was very excited because I thought I had the solution, then I got off and I tested a little bit more, and I realized I didn't have it, and I told you to throw out that entire So I think I think I still have that recording.

Conor

Let me check if I still have that recording. And if we do, maybe at some point um Oh, I think he may be right.

Bryce

Ha! My intuition was correct. I do think you can do it in a single pass.

Conor

Well, no, no, so but this this is a single pass, but this is multiple algorithms.

Bryce

So this doesn't um this doesn't Yeah, but I'm but I'm no no no no because I'm pretty sure that all of these can reduce down into like a single a single kernel.

Conor

Uh potentially. So wait, so let's finish uh reading the tweet. So um Tristan says, uh, can you use this approach? I'm not sure what the thrust segmented algorithms do. And then his code, um, it's using a library that he's written called flow, which is similar to ranges, but has a couple different design. Um but basically it's sort of composable algorithms. So it starts off with a group by using uh a binary equal predicate and then does a map of the sum operation and then just calls a maximum, which basically is if you uh click on this tweet, uh you'll see that I responded, and my response shows the Haskell solution, which is literally the composition of an algorithm called group, which is just a shorter way to spell group by with the uh equal predicate, and then mapping some over each of those groups and then taking the maximum. So basically, you're chunking together all contiguous ones and all contiguous zeros into separate lists. So you end up with a list of lists, and then you just sum each of those sub-lists. So all of the consecutive zeros are just gonna become zero, and then anything with a one is just gonna be equal to the number of ones. So technically, you could also map length instead of sum. They're equivalent in terms of uh the result that they produce. A performance will obviously be a bit different, and then once you do that, you just take a maximum of those values.

Bryce

I I hesitate to say this because this could very well turn into my afternoon being consumed by me once again going and trying to implement this algorithm. Um but I'm pretty sure that that this can be done in a single pass then, because the um the map's just a transform and the max is just a reduction. So those two fuse together, it's just a transform reduce. Um and then the question is can you fuse the group by into that transform reduce? And I th I'm not sure about that, but maybe.

Conor

That's the thing though, is um, and so and so we're sort of skipping around here a bit, so I apologize to the listeners if this is hard to follow, but uh um my journey was from this solution basically, where it starts off with group, which um is just a shortcut. So group is equivalent to the algorithm group by, where you pass it uh equal to. Like so in C bland it stood colon colon equal to. In functional uh languages like Haskell, it's just uh paren equal equal n paren. And basically what that's doing is that is an example of an anamorphism. So you're taking a list and then the result is like a list of lists, and that's the opposite of a catamorphism, where you take a list and then you reduce that down into quote unquote a single value. Um so typically like classic reductions or catamorphisms are summing a list of numbers, taking the product of a list of numbers, taking the maximum, taking the minimum. Um those are all catamorphisms. But and and and we use those all the time. But especially in C LAN, there's a whole category of algorithms called anamorphisms, which are the opposite of reductions. And we don't use those as much because they don't really exist currently in the STL um standard library of algorithms.

Bryce

What is the opposite of a reduction?

Conor

So the easiest example of the anamorphic algorithms is an algorithm like split. So you have a string or a range of characters. And you go from one, and so like classically in Python.

Bryce

I I I tend to think about these algorithms in terms of communication. Um uh, you know, w because I tend to think about algorithms in terms of how you would write them parallel in parallel. And so in in a reduction, it's about everybody communicating information to one point. Um and it sounds like in an anamorphism, it's about um it's sort of like a um uh a fork. It's you got one input you and you split it out into multiple inputs.

Conor

Yeah. And that's the thing, is like this type of algorithm doesn't really exist in pre-C20 um algorithm land. And so that's why we're not, as C developers, we're not very familiar with them. But like when I transition to Haskell and functional programming, there are like a set, they're basically like several different flavors of these, and they are just so extremely useful. So like the classic one is split, where um by default, you know, it it splits on a space. Um, and you can usually customize that. Like if you want to separate a CSV file, a comma-separated value file, you can just split on comma. Um, but there's a bunch of other uh algorithms that are coming in C, you know, 23, 26 ranges that are other versions of anamorphic algorithms. So group by is going to be one of them. Um I'll explain that one last because it's it's the hardest to wrap your head around. The other two that are very popular in other languages go under different names, but typically in Swift and Rust, so I'll use their naming mechanism, are called chunks and windows. Um chunks, basically given a list of 20 numbers. If you want chunks of four, you just specify an integer k equal to four, and then it's gonna give you a list of lists where each of the sublists has four elements in it. And so you're gonna end up with five sublists because 20 divided by four equals five. So chunk is just take take n at a time repetitively. Windows is the exact same thing, except instead of having non-overlapping sublists, they're overlapping. So um, and technically there's usually like a general version of this algorithm called like uh it's it's sometimes it's called windowed, sometimes it's called sliding. And that generalized version, it has uh like three different parameters, and the two key ones are a step size, step size equals X, and then um window size equals y. And so by specifying the step size and the window size to be the same, you get chunks, and to s to specify uh the window size to be whatever X, and then the step size to be one, so you're only stepping one at a time, that's the example of windows. And so sliding go ahead.

Bryce

And and uh for for those who come from the HPC space or the numerical uh uh computing space, um the idea of window is very similar to the idea of a stencil.

Conor

Yeah.

Bryce

Um and the idea of chunks is very similar to the idea of tiling up your problem.

Conor

Yeah, I think literally in um in certain languages, tiling and stenciling is the exact same thing as uh windowing. Uh windowing typically is just in a single dimension, though, um and an APL for sure, uh, when you're doing it in more than one dimension. So in s if you're instead of a list of numbers, you have a matrix of numbers uh that's classically referred to as a stencil or a tile.

Bryce

And windowing is uh something that we very commonly do with C algorithms today. There's a series of algorithms that take two different um input sequences, like there's a overload of transform that does this, there's an um overload of scan that does this, and a common pattern, um, for example, I we used it, uh we use it in that word count example that we talked about two episodes again. A common pattern is to have your two input sequences be from the same actual underlying sequence, where the first input sequence is zero to um uh uh you know n minus one of your input sequence, and the second input sequence is one to n. And so what that gives you is that at each iteration, you're gonna get um uh, you know, like in the first iteration, you'll get element zero and element one. In the second iteration, you'll get element one and element two. That's a window.

Conor

Yeah. And that technically is that is implicitly the behavior that's going on between algorithms, uh adjacent difference and adjacent find. Um they're finding a way to take a window of size two that steps by one. Um so it's a it's a very common in functional languages, they call it zip tail, or like that's the that's the pattern, and there's an um a number of of different names. Um but so so the three out anamorphic algorithms that we've talked about so far, split, uh windows, and chunks, um, they all have something in common. And that's that they specify a list and then a value. So split specify like we were talking about it in the sense of strings, but in Haskell, a string is just a character of arrays. So if you've got an algorithm called split, you could have numbers there and you could just split on the number one or the number 10. It would work just the same.

Bryce

Uh Connor meant to say array of characters, not character of arrays.

Conor

Oh, sorry. Uh a list of characters, yes. Um and for uh windows and chunks, you're just specifying an integer value for the number that you want your window size to be. Um and so these are extremely useful, but then there's a whole other set of anamorphic algorithms that instead of taking values, take either unary or binary predicates. And these are like, in my opinion, what are the truly, truly powerful algorithms? So in the uh problem maximum consecutive ones, this is where we want an anamorphic algorithm that we can actually use a version of this algorithm that takes a unary predicate or a binary predicate. Um so group uh or group by. So group is basically a specialization of group by that already has the binary equal to predicate hard coded in there. But group by, you can specify that binary predicate to be whatever you want. And uh in this case, we would want it to be equal equal, which is why we're just using group. But we could actually use a different version of group by that instead of taking a binary predicate, just takes a unary predicate and says, you know, group by uh some unary function, and whenever it's returning the same value, group those together. And whenever it's returning a different value, you want to start a new sublist. And because we only have ones and zeros, technically you could just do a group by with a unary operation that sort of groups based on whatever that returns. Here, we're always we can just use uh group by and then identity, and so pass it back one one. So basically anytime you get the same value, you're gonna be grouping them. And this is what makes group by confusing is that there's actually like three different implementations of group by. There's the group by from SQL, which is basically not like the two we've been talking about so far. So it doesn't have anything to do with the order in which you are traversing your sort of list of data. It basically is just gonna give you a hash table at the end of the day, and you provide it a unary operation, and that any value that returns you some value X based on that unary operation gets put in a bucket in this hash table. So the easiest example or an easy example is if you have the numbers uh one to ten, and you want to group by, you want to do an SQL group by on whether or not the values are even. And so you're gonna end up with basically a hash table with two keys, uh true and false, and the true one is gonna contain all the even numbers, two, four, six, eight, and then the uh false value is gonna, or false key is gonna have all the odd numbers. If you were to do a group by uh with a unary operation that actually did care about the order, that's what the Python group by from iterTools does. You would just end up with a list of sublists where every sublist is a single character because the uh result of applying even to each number in a sequence one, two, three, four, five, six, seven, eight, nine, ten, is gonna be true, false, true, false, true, false, true, false. So it's just gonna start a new sub list. So basically the SQL group by doesn't care about order, and then there's two types of group by's that do care about order. One takes a unary predicate and only looks at a single element at a time, and then the other one is a binary predicate that looks at adjacent elements. And this brings us full circle back to the by-key algorithms, because that's how I think of thrust by key algorithms is that by key algorithms, correct me if I'm mistaken, but all basically look at adjacent elements to determine the segments. So we're reduced by key is doing uh basically like a single pass reduction. Obviously, in parallel it's gonna be different, but you can think about it like that. And anytime the binary predicate returns uh false, you're gonna start basically a new segment. And so for the maximum consecutive ones, the default binary predicate once again is equal to, and then we just provide another binary operation to do the reduction, which is the sum in this case, and so that's so we call them segmented algorithms because we're providing each of those algorithms with a binary operation or using the default equal to in order to implicitly create these segments within the list, and then on top of that, we're doing a reduction on that list. So, in a sense, the thrust bi-key algorithms are anamorphic behind the scenes, but because we're immediately doing some reduction afterwards, or I guess that's the thing, I'm just talking about reduced by key. Um, so maybe it's useful at this point to talk about uh another.

Bryce

You you are right that it is when you know the the the predicate's always binary, yeah.

Conor

Um so that's that's sort of like you know, you gave an exp explanation at the beginning, and I was trying to map that back to so like whenever I think of by key, I always think of Haskell, or we're gonna be getting a group by in C23 ranges, hopefully, if not 23, you know, a later version, um, a group by algorithm that takes a binary operation, and and that's what I think of. So basically, in a more composable uh algorithm library, you're just calling group by or some anamorphic algorithm that does some segmenting or grouping, and then you're doing something after that. And in Thrust, the way that we give you that m ability is in these by key algorithms that are reduced by key or uh inclusive scan by key. Um so maybe let's think of like what is a I know that there's a unique by key. Um let's like just without looking at the docs, uh a unique by key, so unique in C is doesn't doesn't deduplicate, it deduplicates adjacent equal elements. Yeah, yeah.

Bryce

It's a version in some ways of uh an algorithm that we have in Cobb called uh run length encoding, which is basically an algorithm where it goes and it finds all the consecutive elements and it like compresses them. So if it goes and finds ten elements that are the same, it replaces them with one element and a count of ten.

Conor

Right.

Bryce

So it it which is which is very it's very similar in practice to what what unique is. You can sort of think of it as like a you know a um uh a form of compression almost.

Conor

So yeah, I'm at the docs now, and the example that's shown on uh the thrust docs are our keys are one, three, three, three, two, two, one, and our values are uh nine, eight, seven, six, five, four, three, so just descending from nine. And then when you do a unique by key on those um keys and values, and in this example it's passing in the equal to binary predicate, but I assume that's the default, so technically you can omit that. Uh the result it says is that the first four keys in A are one, three, two, one, which makes sense. Uh, I think. And then the first four values in B are now nine, eight, five, three. Interesting. Is that actually what I would expect?

Bryce

So I'm it's a little it's a little surprising because it um it will discard it it's sort of like um it's sort of like having a map where um uh if you tr if you try to insert an element that already exists, you you know, you discard the um the new element. Um or like JSON has this property too, where like I believe where you can have like duplicate um keys and it it's sort of unspecified in JSON which which of the um of the values for duplicate keys will be uh used. Um and I I'm pretty sure that Throst Unique by Key guarantees that it'll always be the the first one in order. Um

Conor

This is interesting though, because this is almost an entirely different class of algorithm. This is really just stud unique or thrust unique with a second range that follows the same mutation. Whereas reduced by key really is two different operations. It's it's um segmenting and then it's doing reductions on those segments. Whereas here, really, you're just doing uh unique on two different ranges, and you're specifying that you want the keys to be the one where we're determining uniqueness from. So maybe let's let's take a look at a different by key algorithm. We've got reduce by key.

Bryce

Scan by key is not going to be easier. Um key.

Conor

There we go.

Bryce

That's yeah, so sort sort by key is I think more the example that you're looking for. Or we could talk about merge by key, but that's a weird one.

Conor

Yeah, I would assume that sort by key determines the segments based on the binary predicate and then performs a sort operation on each of those segments, which is analogous to the reduce by key, so we're just replacing the reduction with a sort. Our keys are 142857, and then our values are A, B, C, D, E, F. And keys are now sorted. Interesting. Okay, my mind's being blown here. This is not at all. This is this is exactly what unique did.

Bryce

Well, this is this fair s sort sort by key is just saying like like I have separate data and and separate keys. I think one might argue that it's um that naming naming the the scan by key and the reduce by key algorithms, I think are a different class of things than the sort sort by key. Sort by key, and this is this has been one of the criticisms of the by-key interfaces and thrust in general. In Cub, which is the CUDA specific um back end of thrust, there is an explicit notion of segmented algorithms. There's not this notion of by key algorithms. And in Cub, there is a in Cub, the version of this sort is not called sort by key. It's um uh it's just a sort that takes disjoint keys and values. That's what this is. Whereas in Cub, if you want a reduced by key, I believe you need to use a uh uh a segmented reduction. I could be wrong about that. Um uh certain yeah.

Conor

So just because so this is fantastic. I apologize to the listeners if you all are confused, but I just I just learned something um which I think is uh extremely important, and it's that it's exactly what Bryce just said is that really the reduce and the scan um by key algorithms are actually the only ones that are really following this anamorphic uh behavior that I've described, where we're first identifying segments and then doing either scans or reductions on those segments. So far, I we haven't checked them all, but unique by key and sort by key, what they're doing is they're basically just saying perform this algorithm on the keys and then also perform the same mutation on the values. So we're sorting the keys, and uh so basically it's the same behavior. Um wow, wow, yeah, it's the same behavior if if you basically were to just take a single range, put them in pairs, yeah, and uh building up a std unique that worked on a pair and only check the first uh value of that pair, it would be a little bit more complicated to write up. That is essentially what you're doing. You're deleting all uh adjacent equal elements, but only based on the first. And this actually, this is I'm once this is gonna make no sense, but uh there's something from a combinatory logic called the psi combinator, PSI, which is where you perform a uh unary operation on two different things first and then perform a binary operation. So it's similar to the S combinator, which is what's called forks in APL. Um and like what I just said made no sense, but when you uh see the examples, it makes makes complete complete sense. So say I want to take the difference between uh the length of two strings. What do you do in that example? You've got you know the string cat and the string mouse. You first apply the size or the length operation, which gives you three and five, and then you perform the binary minus operation on three and five, which gives you two. So that is an example of using the psi combinator, um, where basically if you compose together minus and size with the psi combinator in a language that supports it, you can just automatically basically feed two arguments cat and most of that, and it works. Which is basically what uh sort by key and unique by key are kind of doing. Note that in uh Haskell, the psi combinator is what's known as on O N from the data.function library. And in APO, we actually have the psi combinator as well. They call it something different. It's um beside, I believe. They just they make names up for things, and uh it's known as like the uh big circle with two dots on top of it. Not that anybody cares. Um are you looking something up?

Bryce

Um yeah, so so I'm looking at what cub does. So cub has a distinct reduced by key and segmented reduced by key. Um uh and there's some nuance in the difference between them. Um uh and and it also um uh I don't believe it it has a segmented sort. It just has the sort and then the sort that takes either disjoint keys or um like disjoint key values or just keys. Um but uh yeah, it it's it's I I I have I suspect that the that the device segmented reduce. Yeah, yeah. Device the the segmented reduction in Cub, um uh I think it can it can work over disjoint regions of memory, um, not just one consecutive region of memory, but I I could be wrong about that. Yeah, yeah, yeah. Um offsets and a number of segments, um uh uh in addition to an input. Um uh yeah. So so I I mean I g I think I would argue that it's probably reduced by key and sort by key is probably a and and like unique by key is probably not a good naming convention because um I think they're actually quite different in how they uh in how they operate.

Conor

Yeah. Arguably, I think sort by key, unique by key, the ones where they're actually doing something by applying the operation to keys, that makes sense. But for reduce by key and scan by key, basically anything that's not applying uh a mutation and is is doing some sort of reduction or operation, those ones should be called like uh group group by reduce or group by, or like that's that's the problem, is that the fact that there's three different definitions for group by, the SQL one, the unary operation one, and then the binary operation one, makes it seem that potentially group by is not the best name, but like identifying the fact that you're doing this, like so yeah, like a segmented reduce or a segmented scan, inclusive scan, might be better because really there's no the fact that you're passing a binary operation to unique by key has nothing to do with segments, it has to do with that's what unique requires. Unique requires and and sort by key, if it's taking a binary operation, the only reason it's taking it is because it's for a custom comparator. It's not it's not actually because it's identifying segments. Um well, that's fantastic. Well, it's fantastic for me because I learned something. Whether or not our listeners learned anything.

Bryce

So I I'm still I'm still wondering whether the max consecutive ones can be done in a single pass. And um uh I'm I'm I'm not I'm starting to become convinced that it that it really can't, because um you sort of need global information um before you can make that decision. Like you sort of you sort of need to look at everything um uh before you can decide whether like who is the maximum one.

Conor

Um but uh but if we had an algorithm called and literally at one point I wrote this algorithm, I didn't call it segmented reduce reduce, I called it sliding reduce reduce because it's such a common pattern. And in APL it is it's so easy to build it up because you just do an nwise reduction, which is a segmented reduction, and then you do another reduction on top of that, and like juxtaposition, literally just putting things side by side is basically composition. But like having the sliding reduce reduce algorithm in pre-ranges land is very, very useful because you're doing a slided reduction and then a reduction over the results of those reductions on the segments. And and that's the thing, is in in ranges land, this becomes such an easy thing to build up because we have an algorithm called sliding that gives us the segments. Then we just map a reduction over those segments, and then we just call another reduction on the result of those reduced segments. And so by just you know composing three different algorithms together, sliding, transform with a reduction in the lambda, and then uh reduction as our final algorithm algorithm, you get uh the behavior that you want.

Bryce

So, so okay, all right, here's a question about maximum consecutive ones. When when we're doing the reduced by key, um within the reduced by key, when we get to the end of a series of consecutive ones, we know it, right?

Conor

Uh the algorithm knows it, yes.

Bryce

Um so then I I mean I think you definitely could do it in a single pass. The simplest solution would be you just have an atomic that would have the maximum length. And when you get to the when you get to the end of uh uh to a run of consecutive ones, you just um you just check that atomic and you you you you you just check that atomic and you and if it's that if the val the value in that atomic is less than yours um than your length, then you try to set it with your length. Um uh and and then at the end you're done. That that atomic should have um the length of whatever the maximum run was. Um I I I do suspect that there's a way to do it without an atomic though.

Conor

Um isn't that just like you're saying just have a global variable that I can write to?

Bryce

Yeah, essentially.

Conor

I mean, isn't that cheating?

Bryce

That's I don't think it's cheating.

Conor

You're so you're calling an algorithm, and then at the end of the day, you don't want any of the output. You don't want the keys, you don't want the values, you can discard all that stuff. What you want is some global that you were writing to in the midst of your parallel algorithm.

Bryce

My point here is that um uh you're you're iterating over this sequence and you're finding you know the length of these um uh uh you know you're finding the length of who who has the lot the um the the longest consecutive sequence. And and um that iteration is yes, it's a reduction, but um it's a like a localized reduction. Um uh and then once you like uh you if you can once everybody's done their localized reduction and found their maximum in the general neighborhood of where they are, then it's just a matter of um uh reducing globally down to who had the greatest one. Um yeah.

Conor

Yeah, definitely that would work. I'm not actually sure you can hook into reduce by key like passing it a custom. You might be able to, like passing it a custom binary operator that basically does equal to, but then in the two different cases, does the writing to the atomic. Um but at that point in time, I don't actually yeah, your your binary operator that's doing the equal to doesn't have the length.

Bryce

Well, so so when we tried to solve this two weekends ago, um we tried to do it with like a reduce that it was a heterogeneous reduce that accumulated into some state, and and that proved to not work because of um uh associativity. Um uh but it's sort of it's sort in the same way that we have like a uh a transform reduce, I think sort of what we need here is like a reduce reduce by key. Um sort of a two-phase reduction.

Conor

That's what I just said, yeah, like a group by reduce reduce.

Bryce

Um reduce reduce.

Conor

It's not that's the thing, is it sounds like a silly name, but in terms of like but that's the thing, is like I'm I'm not knowledgeable enough to know that in Thrust 2.0 land, where we have composable, like parallel composable algorithms, is like I would imagine that a group by reduce reduce is gonna result in like much more performant code than a composed group by and then a transform of a reduction and then a reduction. Like I I don't know enough about compiler optimizations. My guess is whatever parallel code gets launched behind the scenes isn't like equivalent to a single algorithm called group by reduce reduce, where we can definitely implement that algorithm with like the fewest number of kernels as possible.

Bryce

I mean, I I I I I believe that this is something that you can fuse together. Um uh like like ultimately ultimately I think it's just like the underlying kernel is a reduction. And it's just um it's just a question of of sort of like it's this like phased reduction. But yeah, like I think I think if we were, I think in the future, if we were writing this with like uh a range-centric thrust, um, we might have a way of expressing this more natively. Um uh if I don't know. Now that I've had further insight in this, maybe if if I if I had the time this afternoon, I would go back and give it another whirl.

Conor

Every time we start talking about thrust, we end up realizing how come there's no parallel uh fold left? How oh we rate we name these algorithms wrong. It's uh shouldn't be by key. They should be group they should be group by group by. That's all right though. Um I like I said, even if our listeners didn't know.

Bryce

I yeah, I I think that I think that reduce by key probably should have been called group by um uh yeah.

Conor

Group by reduce.

Bryce

Because I think it's sufficiently different from what um oh yeah, yeah, like group by reduce or something like that. Um I think it's sufficiently different from what reduced does. Yeah.

Conor

Which is sad because I just gave uh my GTC talk and uh you know what is continuous lifelong learning though.

Bryce

I will say that the two what two of the coolest continuous lifelong learning is a fancy way of saying we don't actually.

Conor

Um so we gotta talk about C ⁇ now a little bit.

Bryce

Well, I will be giving a keynote at C now on uh what belongs in the uh C standard library. And uh and yeah, it's it's interesting the the act of presenting remotely. Um uh because for GTC we had to record our talks. And usually usually I have like two slides for every minute I'm gonna be talking, and I'm usually end up going a little bit long on content. Um but I found for like pre-recorded talks, I uh I was like way short on like I I I went at a much faster pace through my slides. And I think it's because with pre-recorded, um you're you're able to stop yourself from going off on tangents. I think that's it. It's like it's just better discipline and control. But yeah, I think the keynote's gonna be interesting. I I think that there's gonna probably be three pillars to the talk. Um, and those pillars will probably be the question what belongs on the C standard library, and sort of like fundamentals of standardization and the challenges associated with it and what the standard library is. And then I think that there'll be a a pillar where we'll talk about um stability versus velocity, and we'll talk about what ABI is, and then I think we'll we'll end up talking about um package management um and then build systems by way of that. Um so yeah, I think that's I think that's what the talk will will be about. Um yeah.

Conor

You heard you heard that I retired from speaking?

Bryce

You retired from speaking?

Conor

Yeah, you didn't uh you didn't tweet about it. I tweeted that because Tony was Tony's giving one of the keynotes.

Bryce

Yeah.

Conor

And someone he tweeted about it, someone responded to him saying, Oh, that means you won't be able to win any awards this year because keynotes aren't eligible. And then Tony for some reason blamed me and said that I spoke to the committee and obviously changed the rules to get that to happen, and then uh so that I could win more awards. And I said, Haven't you heard, Tony? I've retired.

Bryce

You've not retired.

Conor

You just gave a talk.

Bryce

You just gave a talk.

Conor

Yeah, I know. That was my last talk. That was my 20th talk in just over two years, of which 55% have been virtual. So I have now spoken more virtually than I have in person. And uh I've retired at the young age of uh 30.

Bryce

And uh I I am not uh are you 30 already? Are you older than me? Like by a by a few months? Yeah, you are. You are older than me by a few months. Yep. I'll be 30 in in July, folks. Which me which means the uh the the jokes about me being young uh can can uh can can terminate now.

Conor

Well no, so that that's the other thing that you're gonna have your birthday in Canada next year. Is that right?

Bryce

I'm gonna have my birthday in Canada next year?

Conor

Yeah, 2022, CPP North.

Bryce

Oh yeah, yeah. Yeah, I can do that. Yeah.

Conor

We got lots of we got lots of waterfowl here, bro. I'll take you to the I'll I'll take you to the ponds. We got a beaver. Um we had two beavers, but then one of the beaver uh was either killed or or would died.

Bryce

Maybe I'll save my um well well should should I I I have I know the I know a good talk that I would give, but should I should I ruin it? Should I spoil it for the audience?

Conor

Sure. We always that's a thing. I've spoiled my tattoo, my my my APL talk. We just anyone that listens to this podcast.

Bryce

Wait.

Conor

Remember? We're getting the rotate tattoo.

Bryce

Uh that's right. That's right. Uh I but here's the thing, no nobody's gonna rem I I can spoil it now because it's a year off, nobody's gonna remember, but I have a talk about naming that I'd like to give. And so maybe I'll give that at uh at C plus C North.

Conor

Thanks for listening and have a great day.