Rendered at 06:04:09 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
hatthew 7 hours ago [-]
Are we talking about this from the perspective of CS (algorithm optimization) or SE (code design)?
From an SE perspective, make a flatmap function that explicitly handles Collection<Optional<Walrus>>. The implementation doesn't matter. If your language/framework already has a compatible flatmap function, make a single frobnicate(Optional<Walrus>) function that returns whatever value is necessary for flatmap(frobnicate) to discard them.
From a CS perspective, doing a filter from Collection<Optional<Walrus>> to Collection<Walrus> is probably a bad idea. If your collection is small, nothing matters. If your collection is large, you probably don't want to spend time making a new copy of it. If your filter just returns a view rather than a hard copy, then there is no optimization benefit and you should just do whatever makes the most sense from an SE perspective. If frobnicate is cheap then you're paying the branch prediction failure tax anyway regardless of when you frobnicate, and if frobnicate is more expensive then your should probably parallelize and have each thread handle unpacking the Optional. Either way, you probably don't want to spend time making a copy.
These are all generalizations based on hypotheticals and there are certainly a lot of exceptions, but broadly speaking I don't see a strong argument here. If optimization matters then optimize based on your own profiling of your situation, and if optimization doesn't matter then design your functions based on what features and paradigms are available/common in your area.
socializer 10 hours ago [-]
I am continually impressed by the ability of LLMs to take trivial ideas and turn them into lengthy and obtuse blog posts with unnecessary analogies.
globular-toast 17 minutes ago [-]
To be fair, I bought Martin Fowler's Refactoring book expecting to learn a bunch of new stuff and up my game. What I found was a bunch of stuff I knew already just from experience but thought was too obvious to enumerate and write down. This was all written a long time before LLMs, of course.
I think the main reason I don't write more is I think once I've thought through something it's too obvious to write down. I consider it a defect of mine and sometimes have to force myself to write.
cryptonector 2 hours ago [-]
No, TFA is standard fare for bloggers who dwell in category theory 24/7.
adamgordonbell 6 hours ago [-]
Debasish has been writing for a long time. I have one of his books on FP in my office.
swiftcoder 10 hours ago [-]
Honestly, this just looks like one of those lingo-heavy-but-surface-level blog posts that used to make functional programming spaces so insufferable to everyone on the outside
ahartmetz 7 hours ago [-]
You want to avoid branches in hot paths. If you branch inside the loop, lots of branches. If you branch outside the loop (into different specialized loops), few branches. Big fucking deal.
bool w;
int x[1000];
int y[1000];
for(int i=0;i<1000;i++){
x[i] += y[i];
}
if(w){
for(int i=0;i<1000;i++){
y[i] = 0;
}
}
Slower but prettier with the if pushed down.
theteapot 5 hours ago [-]
Is that true? The loop is a branch.
bathtub365 5 hours ago [-]
Who actually considers a loop a branch? A loop is a chunk of code that will be run N times depending on some evaluation that’s run before or after an iteration. A branch is a single decision which of several pieces of code to run, once.
pasquinelli 3 hours ago [-]
> Who actually considers a loop a branch?
interesting question...
> A loop is a chunk of code that will be run N times depending
Just a simple example, but the conditional branch occurs on line 14 of the generated assembly. It does a comparison (line 13) and then a conditional jump (jl, line 14).
ngaiorn 4 hours ago [-]
>Who actually considers a loop a branch?
The machine?
ahartmetz 4 hours ago [-]
The "go around" branch is obviously not completely avoidable (though unrolling helps). Branches inside the loop body sometimes are.
mahboi 9 hours ago [-]
These things are so divorced from the reality of programming, even when they involve actual code instead of fancy lingo. Like in Scala, not a pure functional language, tutorials used to find the most convoluted higher-order functional way to do simple things.
robofanatic 7 hours ago [-]
And also generate a shorter version.
mathisfun123 7 hours ago [-]
Semantic compressor and decompressor
bioneuralnet 10 hours ago [-]
Yet another encroachment on traditionally human activity.
moritzwarhier 10 hours ago [-]
I am the
Option<Walrus>
msdz 8 hours ago [-]
I know we’re not supposed to comment just for that, but this might be my single favorite joke comment I’ve ever read here. Good job.
Except that TFA is a bog standard example of traditional human activity and the GP's comment is nonsensical trolling.
jampekka 8 hours ago [-]
[flagged]
socializer 8 hours ago [-]
It's an LLM-generated article.
jampekka 8 hours ago [-]
How do you know? Doesn't read particularly LLM written to me, and even if it is, it's quite well written.
The author has been blogging about this kind of stuff for over 20 years. I'd be surprised if they suddenly let bots autonomously spam their blog.
raphman 7 hours ago [-]
Yeah, sounds human-written to me, too. Out of habit I checked with Pangram - which identifies some parts as AI-written. (I think that might be false positives but am not 100% sure.)
jchw 5 hours ago [-]
This particular blog post didn't really stand out to me as having a lot of Claudeisms, though there were some parts of it that definitely made me raise my eyebrow. Just little things here and there, and also near the end where the words "pay" and "cost" stand out a lot in the particular ways they were used.
I was curious if this was just my own stupidity, just human pattern matching run amok. So I decided I'd go back a bit and check another article to see if it felt like that. My initial plan was to go far back enough to when AI usage would've been less likely, but as far as I can tell this particular blog only goes back this far, from april of this year:
Ah, there's our friend Claude. I dunno if I got lucky and picked literally the only article with "load-bearing", but I promise, it wasn't my intention.
This newest article is a lot less egregious in terms of LLM-isms, but I can see why it triggers the AI spidey senses too. If I had to guess I'd be more conservative and guess it was AI-assisted rather than AI-generated, although I'm sure some people have some skills or tools that can do a relatively good job weeding out the more overt LLMisms and even give Pangram a bit of trouble.
Of course in reality, given how many full complete blog posts with things like diagrams they have since April of this year, I think it's only reasonable to say that it would be a little on the surprising end if they weren't using LLM assistance.
It's sad that it's come to this, though. Some days it feels like I'm in a weird low-stakes version of The Thing.
I've always believed the opposite: get conditionals deep in your code so that the higher level control flow is regular.
But I suppose my greater philosophy for making code that avoids bugs is that you have a couple things that are done when dealing with data:
- distribution
- deciding
And you want to avoid distribution and deciding being mixed together in the same spot.
"Distribution" can be for loops but also breaking up some data based on some key into N bistinct buckets
"Deciding" is where you're looking at the data more closely to make some decision (like "is this a big customer or a small customer")
Distribution often involves decision making, but if you mix them all in one spot you can obfuscate your decision points. Splitting it up just makes things "obviously" right or "obviously" wrong. Perf stuff is another discussion of course, but in practice most things are not at a scale where it matters.
by_category = defaultdict(list)
for d in data:
by_category[category(d)].append(d)
for category, per_category_data in by_category.items():
do_thing(category, per_category_data)
I really value code patterns that make mistakes obvious, or at least makes it harder to stuff a mistake in somewhere. Some patterns are harder to describe in this model though.
(I do like the advice of having a consistent vocabulary for working on collections as a principle though, I just find that top-level conditional use tends to quickly get you into "... why is this method not called" territory, which is a more annoying problem than "why is this slow")
sigbottle 6 hours ago [-]
That's another good way to look at it - sometimes the "base" is more like a physics substrate. Physics doesn't care about semantics, it just is. Putting semantics first would be weird.
I guess it's a case of perspective
whilenot-dev 2 hours ago [-]
If the data don't need to be processed in batches by category, and if the category is derivable from an data item alone, I don't really see the benefit you're proposing.
Even worse, by splitting one state (and one derivable category from that state) into two separate arguments for do_thing, something can be off rather badly. I'd then feel the need to design an assertion of the relation of the arguments in order to make things bearable again:
def do_thing(category, data):
# to avoid shadowing lets rename your function `category` to `category_from_item`
assert all(category == category_from_item(item) for item in data)
...
But that would add a third loop to your two loops, and would duplicate the computation of a category.
Instead, if category would be a property of data item:
class DataItem:
@property
def category(self):
...
...and the do_thing function would work on a single data item, then it'd just become a simple matter of one for loop and one match/case:
def do_thing(item):
match item.category:
case ...:
...
a1o 6 hours ago [-]
I think compilers can push out ifs inside for to be one if with two fors.
ninalanyon 9 hours ago [-]
I've done this for years. Not every time of course but where it makes the code easier to understand and maintain.
Speed was almost never the reason.
alterom 9 hours ago [-]
I take it you never rewrote a Matlab for loop as a vector/matrix op for insane speedups then :)
andy_ppp 1 hours ago [-]
Idiomatic Elixir does this with pattern matching on function parameters so you end up with things like the following, raw if statements are discouraged because of this:
def classify(:ok)
def classify({:error, reason})
def classify([first | rest])
def classify(%{name: name, age: age}) when age >= 18
def classify(%{name: name, age: age}) when age < 18
wallstop 9 hours ago [-]
What is missing here is any benchmarks backing up this argument for code structure.
The same technique is applied as an optimization, when deemed safe, in all current gen c compilers (gcc, llvm, etc).
I'm very confused why neither measurements nor references to when this is done automatically in most modern languages is included in the article.
cogman10 9 hours ago [-]
At least in JVM land, it's pretty easy to thwart that optimization. Particularly if the condition is on a mutable yet unchanged in the loop value.
For example:
var map = new HashMap<String, String>();
map.put("foo", "bar");
for (var i : items) {
if ("bar".equals(map.get("foo")) {
doStuff(i);
}
}
Even though `map` isn't mutated, it's hard enough for the JVM to detect and the underlying `get` functions are complex enough that it'll run the `get("foo")` every time, which can be quite expensive.
Maxatar 8 hours ago [-]
Can't speak for C# but in C/C++ the optimization can rarely be applied safely due to aliasing. If any part of the data you're working with involves a char* then C/C++ optimizers refrain from doing these kinds of optimizations because of how difficult it is to guarantee the absence of mutability.
gorgoiler 8 hours ago [-]
Erm, no? You write f(w: Walrus) -> Walrus and then let the caller handle Walrus|None and Iterable[Walrus] however they wish!
And if someone decides the codebase needs an abstraction over (and therefore specific functions to handle) Iterable[Walrus|None] then you check the weather and suggest they take a break and go for a stroll. (You check the weather to see if you should lend them your brolly.)
What am I missing?
Chinjut 37 minutes ago [-]
Push lists of 0 or 1 up, but push lists of 0, 1, 2, 3, or etc, down?
dieselgate 9 hours ago [-]
Didn’t see it mentioned in the article but isn’t leading with if-statement called a “guard clause”.
I like that pattern but it’s just general best practice I thought.
They're one of those good practices that look like bad practice to everyone who just got a CS degree. Seems ex-students are unsettled by asymmetry or want to minimize the number of return statements.
mahboi 6 hours ago [-]
Oh, guard clauses might also use continue statements in loops
ramesh31 8 hours ago [-]
Particularly for high performance code when branch prediction is taken to account
econ 7 hours ago [-]
Why am I even in this function if it shouldn't happen?
preg_match 4 hours ago [-]
Because a lot of languages can't easily express the semantics of "you're not allowed to call the function this way". I mean, suppose you have a function that takes two integers, but the second must be greater than the first. Most type systems don't have any way to represent that at all, let alone conveniently.
cowlevel 6 hours ago [-]
Related: don't write if(arg == null) throw ArgumentNullError; at the top of every function. If it's not supposed to happen then just let it throw the error naturally when you dereference it. In C it's even worse because you turned an easily caught segfault into a silent nop.
mahboi 6 hours ago [-]
If the criteria for it not happening is too complicated to expose to the caller
6 hours ago [-]
woadwarrior01 8 hours ago [-]
Swift explicitly has a guard statement for this. Rust's let .. else { ... } is also very similar.
I have always phrased this as "Never do one of something".
narnarpapadaddy 7 hours ago [-]
“Batch is the primitive”
throwawayffffas 8 hours ago [-]
Just the branch predictor gains are probably worth it.
sigbottle 6 hours ago [-]
Is the idea that "accidental casework" should be moved up, whereas the "reusable bulk ontology" should be moved down?
There's very high-leverage abstractions that completely constrain a space. An example is a good definition - you can't think of something outside to compare it to, it just is. These things survive for a long time since they define it.
But if you're trying to do that philosophy super deep into a program, you're probably violating a bunch of invariants subtly.
Of course, there is no good separation at the end of the day as we all know from spaghetti codebases :)
4b11b4 7 hours ago [-]
this is just a guard on the function definition?
OutOfHere 9 hours ago [-]
I like it, but to do fizzbuzz in this way, you'd have to separate what's inside the loop into a reused function.
pdpi 9 hours ago [-]
I think it's sort of obvious that the limit to this general rule is when data dependencies between fors and ifs forbid you from pushing things further up/down.
taolson 8 hours ago [-]
Or just use lazy list operations with a single if test at the end:
"the loop runs without a branch, and is a candidate for vectorization".
That's it, that's the article. This matters a lot in huge-scale / scientific computing / HPF, where if you can express something as an operation on vectors on matrices, you win big (those ops parallelize well, can be run on GPUs, clusters, what have you).
mypalmike 6 hours ago [-]
For the 99% of developers who are shuffling data around constrained by I/O, you win small.
From an SE perspective, make a flatmap function that explicitly handles Collection<Optional<Walrus>>. The implementation doesn't matter. If your language/framework already has a compatible flatmap function, make a single frobnicate(Optional<Walrus>) function that returns whatever value is necessary for flatmap(frobnicate) to discard them.
From a CS perspective, doing a filter from Collection<Optional<Walrus>> to Collection<Walrus> is probably a bad idea. If your collection is small, nothing matters. If your collection is large, you probably don't want to spend time making a new copy of it. If your filter just returns a view rather than a hard copy, then there is no optimization benefit and you should just do whatever makes the most sense from an SE perspective. If frobnicate is cheap then you're paying the branch prediction failure tax anyway regardless of when you frobnicate, and if frobnicate is more expensive then your should probably parallelize and have each thread handle unpacking the Optional. Either way, you probably don't want to spend time making a copy.
These are all generalizations based on hypotheticals and there are certainly a lot of exceptions, but broadly speaking I don't see a strong argument here. If optimization matters then optimize based on your own profiling of your situation, and if optimization doesn't matter then design your functions based on what features and paradigms are available/common in your area.
I think the main reason I don't write more is I think once I've thought through something it's too obvious to write down. I consider it a defect of mine and sometimes have to force myself to write.
interesting question...
> A loop is a chunk of code that will be run N times depending
oh! turns out you do!
A loop contains a branch - keep looping or break. At least according to structured programming -- https://en.wikipedia.org/wiki/Structured_program_theorem.
Just a simple example, but the conditional branch occurs on line 14 of the generated assembly. It does a comparison (line 13) and then a conditional jump (jl, line 14).
The machine?
The author has been blogging about this kind of stuff for over 20 years. I'd be surprised if they suddenly let bots autonomously spam their blog.
I was curious if this was just my own stupidity, just human pattern matching run amok. So I decided I'd go back a bit and check another article to see if it felt like that. My initial plan was to go far back enough to when AI usage would've been less likely, but as far as I can tell this particular blog only goes back this far, from april of this year:
https://debasishg.github.io/blog/pin-dyn/
Ah, there's our friend Claude. I dunno if I got lucky and picked literally the only article with "load-bearing", but I promise, it wasn't my intention.
This newest article is a lot less egregious in terms of LLM-isms, but I can see why it triggers the AI spidey senses too. If I had to guess I'd be more conservative and guess it was AI-assisted rather than AI-generated, although I'm sure some people have some skills or tools that can do a relatively good job weeding out the more overt LLMisms and even give Pangram a bit of trouble.
Of course in reality, given how many full complete blog posts with things like diagrams they have since April of this year, I think it's only reasonable to say that it would be a little on the surprising end if they weren't using LLM assistance.
It's sad that it's come to this, though. Some days it feels like I'm in a weird low-stakes version of The Thing.
But I suppose my greater philosophy for making code that avoids bugs is that you have a couple things that are done when dealing with data:
- distribution
- deciding
And you want to avoid distribution and deciding being mixed together in the same spot.
"Distribution" can be for loops but also breaking up some data based on some key into N bistinct buckets
"Deciding" is where you're looking at the data more closely to make some decision (like "is this a big customer or a small customer")
Distribution often involves decision making, but if you mix them all in one spot you can obfuscate your decision points. Splitting it up just makes things "obviously" right or "obviously" wrong. Perf stuff is another discussion of course, but in practice most things are not at a scale where it matters.
I really value code patterns that make mistakes obvious, or at least makes it harder to stuff a mistake in somewhere. Some patterns are harder to describe in this model though.(I do like the advice of having a consistent vocabulary for working on collections as a principle though, I just find that top-level conditional use tends to quickly get you into "... why is this method not called" territory, which is a more annoying problem than "why is this slow")
I guess it's a case of perspective
Even worse, by splitting one state (and one derivable category from that state) into two separate arguments for do_thing, something can be off rather badly. I'd then feel the need to design an assertion of the relation of the arguments in order to make things bearable again:
But that would add a third loop to your two loops, and would duplicate the computation of a category.Instead, if category would be a property of data item:
...and the do_thing function would work on a single data item, then it'd just become a simple matter of one for loop and one match/case:Speed was almost never the reason.
Of note, as of C#9 (and maybe prior), the dotnet runtime does this automatically whenever it is deemed safe. https://devblogs.microsoft.com/dotnet/performance-improvemen...
The same technique is applied as an optimization, when deemed safe, in all current gen c compilers (gcc, llvm, etc).
I'm very confused why neither measurements nor references to when this is done automatically in most modern languages is included in the article.
For example:
Even though `map` isn't mutated, it's hard enough for the JVM to detect and the underlying `get` functions are complex enough that it'll run the `get("foo")` every time, which can be quite expensive.And if someone decides the codebase needs an abstraction over (and therefore specific functions to handle) Iterable[Walrus|None] then you check the weather and suggest they take a break and go for a stroll. (You check the weather to see if you should lend them your brolly.)
What am I missing?
They're one of those good practices that look like bad practice to everyone who just got a CS degree. Seems ex-students are unsettled by asymmetry or want to minimize the number of return statements.
https://docs.swift.org/latest/documentation/the-swift-progra...
There's very high-leverage abstractions that completely constrain a space. An example is a good definition - you can't think of something outside to compare it to, it just is. These things survive for a long time since they define it.
But if you're trying to do that philosophy super deep into a program, you're probably violating a bunch of invariants subtly.
Of course, there is no good separation at the end of the day as we all know from spaghetti codebases :)
https://github.com/taolson/Admiran/blob/main/examples/fizzBu...
/s
"the loop runs without a branch, and is a candidate for vectorization".
That's it, that's the article. This matters a lot in huge-scale / scientific computing / HPF, where if you can express something as an operation on vectors on matrices, you win big (those ops parallelize well, can be run on GPUs, clusters, what have you).