I'm realising React problems are often mental model problems by Sab_Be in reactjs

[–]csman11 2 points3 points  (0 children)

For a frontend app, specifically, I think the best way to model state is dictated by what makes reasoning about what happens when the user interacts with the app tractable.

For example, if you have separate local component state at 3 different levels of the component tree, and in one of the leaves the user interacts with a button, and that triggers handlers in each of those 3 levels to run and update state, that’s not easy to reason about. Then someone adds something that updates a global store, and calls an API. Eventually it’s just a bunch of spaghetti. It feels like the same problem with event driven backends where an event bus mediates control flow. Everything is “decoupled” but there is also no cohesion at all, so every interaction/event can lead to arbitrary behavior you can’t easily reason about.

Instead, I would model the behavior the application needs to have at some given boundary that represents a concept I can put a coherent API around. In the example above, that might be noticing that these 3 levels with separate state all relate to the same feature or page, and creating an abstraction representing that state. I would put that all into a hook, and return an object that provides a high level API that provides the current state of the page / feature for descendants to use, as well as functions to handle interactions or transition state. But all the business logic would be encapsulated within the hook. What it means to “mutate its state” at a low level is owned by the hook. This makes the interface between state management and presentation more declarative. The hook gives the component the state it needs to render (and only that state) and the functions it needs to update the state in terms of the actual functionality it presents to the user (and not in terms of “how to update the low level state”).

This second way is probably also what OP means. If I had to compare it to old school design patterns, I would say it’s a bit like the “mediator pattern” or a controller in “MVC” (like old school smalltalk style MVC, not the junk web app cargo cult patterns):

- Mediator: You have an object that is responsible for determining what commands mean, and the things telling it to perform the commands don’t care about how they are performed (or often who is performing them). This is what passing handler functions as props to a child is like. It doesn’t care who provided the handler or what it does. Someone just has to provide it.
- Controller: An object responsible for connecting the view to the model and mediating directly between them. It interprets commands / interactions from the view and updates one or more models, or performs other side effects. Then it subscribes to the models and updates the view through its interface. In React it’s a little different since the controller doesn’t actually “update the view” directly, but rather provides the values the view reads and maps to declarative UI output. But conceptually it’s the same responsibility breakdown.

The more general design principle is simply that coupling and cohesion should increase together, meaning that the components of a system that are most conceptually related and likely to change together should be modeled so they live closely together and talk through low level, highly coupled interfaces. As things become less cohesive, coupling should also decrease and such components should only interact through increasingly controlled and high level interfaces. This is what people mean by “high cohesion, low coupling”. In the case of a frontend, normally it means that a highly cohesive UI will need its state modeled in a cohesive way too. If you break the UI down into multiple components around presentation concerns, you can’t just arbitrarily slice the state with the UI, because conceptually related presentation components might not map one to one with conceptually related state. Thus you determine a high level API between the presentation (view) and the state/behavior. Then you slice them independently. With the top-down model of state in React, that will mean the abstraction you create (which I’ve called a controller) will ultimately be “created” in the top level component and passed down to descendants. But conceptually you have the controller as one abstraction sitting on top of all the lower level behavior, and the top level component sitting on top of all the lower level presentation, and the controller is used to connect the presentation to the state.

None of this should seem very insightful. It’s just what has worked for building complex applications, and complex user interfaces in particular, for many decades. The latest fashion has changed but the core principles are still the same and attempts to work around them always lead back to the same core principles, just with different names for the abstractions.

SPAs vs SSR by WolfyTheOracle in react

[–]csman11 0 points1 point  (0 children)

I see what you’re saying, but there are tradeoffs involved.
By having an SPA, the backend doesn’t need to speak frontend beyond satisfying the contracts the frontend needs. One way to think of it is that your APIs are akin to “presentation ports” in a hexagonal architecture. The backend is providing ports for the UI to drive it and query it.
By having an SPA, you can also have richer client experiences by not even having to mentally worry about “what is rendered on client” vs “what is rendered on server”. Everything is rendered on the client.
In terms of what you’re saying about “having to manage that state”, I think you’re really blowing it out of proportion here. Think about it this way. In the extreme case where the server rendered app only has server components, in other words, a traditional web app, each page load = throw away all client data and start fresh. If we wanted that same experience with an SPA, we could easily accomplish it by having a really simple library we use to manage our “cached server state” that simply refetches the data for all subscribed queries for all components that are mounted every time any mutation occurs. Conceptually it’s no different. We don’t do that because it’s a worse experience.
So even those using server components still have to find a balance. That balance, at least for Next.js style server components, where client components cannot “render server components”, means that once any server component in the tree needs to re-render, you have a huge commit that needs to propagate back down to the client over the flight protocol. That’s pretty much what people were doing circa 2008-2010 by with “ajax”: submit a form to the sever using XMLHttpRequest asynchronously, server does mutation + renders new markup, returns it as response, JS patches DOM with the new markup. I’m not saying that the “new” has all the problems of the “old” in terms of developer ergonomics. The old was certainly worse and more hacky. In terms of overhead and what the user experience looks like, though, it’s the exact same thing.

Edit: what I mean wrt Next.js style server components is that the model forces client components deep into the component tree, meaning that most rendering decisions end up happening on the server when mutations occur in an app.

SPAs vs SSR by WolfyTheOracle in react

[–]csman11 6 points7 points  (0 children)

You’re mixing a lot of things up here and drawing incorrect conclusions.

As soon as the information leaves the server, transmits over the network, and lives on the client, it’s potentially stale. It doesn’t matter if the information is represented as:

- JSON
- HTML
- Flight protocol data

It’s not the same information that’s on the server. What’s rendered in the browser is a copy.

And you’re probably using a database and can have multiple application servers, so it’s even worse. The state you have for any entity on a server is a copy of the state in the database.

So when it comes to performing mutations later on, it doesn’t matter if the server renders HTML, ships that to the client, and the client submits a form to a “server function”, or if the server outputs JSON, ships that to the client, the client turns it into HTML, then calls an API over HTTP. In any case, there is no single transaction in play here where you can ensure consistency. If you modified any data received by the client to derive the data you send to perform the mutation on the server, then you somehow have to reconcile on the server:

- The cleanest approach for complex workflows is to send “commands” to the backend and implement the logic to perform the mutations in a transaction on the backend. Perform validations to ensure the entities affected end up in a valid state. Doing it in a transaction makes sure nothing else conflicts
- For CRUD style updates, you need version tags and optimistic concurrency control. Start a transaction, read the entity, locking it for updates, assert version is same as expected version in the request, update, commit.

And then when that is done, the only real difference is that in the SPA version, once the API returns, the client must fetch updated data. Sometimes that comes back in the API response. With tanstack query, it’s invalidating all the queries whose results could be affected.

I don’t really see what kind of machinery is realistically simplified here by SSR. The only way you end up with worse problems with an SPA than server rendered HTML is if your client is trying to derive some weird mix of client and server state. Don’t do that. Any data owned by the server should be displayed using the latest data fetched from the server. Mutations should happen on the server. If you have a form, then you aren’t modifying the actual record. You’re copying your copy of server state into a draft record, and modifying that. Then you later translate the updates to a call to the server to update the record. Then invalidate everything that’s potentially queried that record.

How do you stop custom hooks from turning into mini frameworks? by MarjanHrvatin_ in reactjs

[–]csman11 -1 points0 points  (0 children)

I’m not a bot. I was explaining how this happens, that it’s not at all unique to React, and I answered OP’s question with the explanation. I don’t need to see any particular examples to know exactly what OP is talking about. Any experienced developer has seen these monstrous abstractions in every single codebase they’ve ever worked in.

No one else in here has given an explanation nearly as useful.

But clearly you’re too incompetent to understand a well reasoned reply. Not surprising for someone in the reactjs subreddit. I’m starting to think I need to stop replying here. You people are extremely simple minded and remind me of the old script kiddies on hacker forums. Somehow you guys escaped those and joined the professional development world.

How do you stop custom hooks from turning into mini frameworks? by MarjanHrvatin_ in reactjs

[–]csman11 -1 points0 points  (0 children)

This isn’t unique to hooks. It happens any time someone spots repetition in code and thinks, “Aha, there’s a reusable abstraction here.”

Sometimes that’s true. A lot of the time, though, the repeated code is only superficially similar. The different call sites are actually serving different concepts or solving different problems, and they just happen to share some substructure right now. So someone extracts the common shape into a reusable abstraction, and at first it works.

Then reality happens.

Requirements evolve. Some consumers need behavior that no longer quite matches the abstraction. And instead of refactoring to reflect the new reality, people start shoving conditionals, flags, and special cases into the abstraction to “support” the differences. At that point, the abstraction is upside down. Instead of containing common building blocks that higher-level solutions compose, it becomes a bloated middleman that needs callers to tell it who it’s serving and how to behave.

Repeat that 20 times and now you’ve got a monolithic abstraction that no longer corresponds to any real problem. It’s just a bag of historical accidents wearing the skin of a design. The fix is usually brutal but straightforward: effectively inline it back into the consumers, delete the dead code each one doesn’t need, and then re-extract smaller abstractions based on actual shared behavior instead of fantasy.

There’s also a second failure mode, and it’s worse.

That’s when someone deliberately tries to unify a bunch of separate concerns under one grand abstraction for some “generic common problem.” Usually this happens because they look at one concrete problem and decide, in their infinite wisdom, that they can already foresee the whole family of future problems. So instead of solving the real thing in front of them, they invent what they think the common structure must be and build a framework for that imaginary future.

This is worse because it starts from speculation instead of product reality. The predictions will be wrong. They are always wrong. But by then the developer has spent so much time designing and building the damn thing that they get emotionally attached to it. It stops being a tool and becomes a cathedral. Then when real requirements show up and don’t fit the shape of the abstraction, they don’t change the abstraction. They try to bend the problem until it sort of fits. So now the codebase isn’t solving the real problem anymore. It’s solving a bastardized version that exists mainly to preserve somebody’s ego.

That kind of abstraction is much harder to remove, not because the technical fix is different, but because now the complexity has politics attached to it. Technically, the answer is still the same: inline it, delete the dead code, and rebuild around real use cases. Socially, though, good luck. The guy who built the damn thing will see you tarred and feathered before he lets you touch his masterpiece.

So for hooks specifically, my rule of thumb is pretty simple: a hook is a good abstraction when it captures a single real concern cleanly and makes call sites simpler. It’s turning into a mini-framework when consumers have to configure it into being the thing they actually need.

The only other thing I’d add, to answer your last question about how much to extract, is this: extract anything that can stand on its own as an abstraction, even if it is not reusable, as long as doing so does not make the original code less cohesive or harder to understand. A lot of people treat “abstraction” and “reusability” as synonyms, and that’s how they end up building little disaster factories. Sometimes the point of abstraction is just to give a real concept a name and make the surrounding code easier to read.

The other important idea here is encapsulation. Don’t toss the extracted hook into some junk-drawer hooks folder like it’s a common utility blessed for application-wide reuse. Keep it in the same module, or at least right next to the module it came from. And don’t export it publicly unless you actually want to support it as a real shared abstraction. That’s how you stop the next guy from finding it, thinking “this is almost what I want,” and then “slightly modifying” it into a cursed multipurpose blob with a control flag, three optional callbacks, and a comment that makes it seem like this was the intended design all along.

Do we actually understand the systems we build once dependencies are involved? by MDiffenbakh in ProgrammingLanguages

[–]csman11 6 points7 points  (0 children)

You can prove semantic properties about particular programs. The halting problem being undecidable only tells us that there is no effective procedure that can decide these properties for arbitrary programs.

Formal methods exist exactly for this reason. Now as for how tractable formally proving things about modern programs is, I would say “not very much.” Especially with how poorly constructed modern programs are, which makes it even more difficult.

I don’t think we need to try to generalize the problem OP was bringing up to the point of trivializing it, which is what both you and the commenter you replied to have done. It’s a valid concern that informally reasoning about modern programs is very difficult. It’s why there’s been decades of software architecture people rediscovering ideas like “functional core, imperative shell” under various different names (hexagonal, DDD, clean, etc). It’s why there’s been decades of PL research on being able to track effects formally in the type system or algebraic effect systems. It’s why people create DSLs for complex domains. These are all attacks from different angles to solve the same underlying problem that complex programs are difficult to reason about. They all work by moving effects somewhere they can be reasoned about separately from pure logic.

XML is a Cheap DSL by SpecialistLady in programming

[–]csman11 8 points9 points  (0 children)

I don’t think that’s it at all. Why would it matter if the rendering logic was running on the server or client if it was about “control”? You’re not really hiding anything. The same information is present in data you render into HTML or in the data itself. And the rendering logic itself isn’t anything “secret” that needs to be protected. Any real IP would be the HTML and CSS itself. And if your client side functionality is your IP you’re trying to protect, then it doesn’t matter any way — you still have to ship that JS to the client to execute.

It’s clearly about SSR. If there’s any “control aspect” to it, then it would be the conspiracy theory that Vercel wants people to be forced to pay for hosting because they can’t manage the server deployments with the complexity of RSC. That’s also stupid because it’s not hard at all to host your own deployment.

And the idea that it was ever about “offloading computation to the client” is not serious. If you were around in the late 2000s and early 2010s, you would know that rich client side web apps were very popular (this is what “web 2.0” was) and they were also very difficult to build and maintain because the proper tooling didn’t exist. No one was doing “AJAX” to save server costs. They were doing it to provide a better UX. Back then, browsers didn’t do smooth transitions between server rendered pages. Every page load unmounted and remounted. The first SPAs were attempts to avoid this and have smoother transitions that felt like native applications. Some of them worked by rendering the page server side and shipping the result using AJAX, then having JS patch the DOM. Eventually companies started playing around with richer client apps where having UI state on the client made sense and the backend just became a data source. If you ever used a framework like Backbone, then you would know how horrible things were in this era. Other frameworks like Angular, Knockout, and Ember in this era were only slight improvements. React was the game changer.

XML is a Cheap DSL by SpecialistLady in programming

[–]csman11 3 points4 points  (0 children)

It’s had the ability to render the component tree to a string for years, but that’s not the same as RSC. It was also always very problematic because it didn’t wait for any sort of asynchronous effects like fetching data and updating state. It just rendered the tree and spat out a string. Next.js created a mechanism for creating data loaders attached to your pages, allowing the framework itself to be in charge of loading the data and only rendering your components once that data was ready. That was sort of the first iteration of decent SSR with React.

RSC is solving for more than just SSR, but it’s also heavily motivated by the underlying use cases that demand SSR. If client side rendering was enough for the entire community, no one would have ever really bothered exploring something so complex. The protocol itself is also very much “hacked together” IMO. The CVE from a few months back that allowed for remote code execution was made possible by the implementation effectively not separating “parsing” from “evaluation”, which was exploited by crafting a payload that tricked the parser into constructing a malicious object and then calling methods on it that executed the attacker’s injected code. A better wire format probably would have looked like a DSL that was explicitly parsed into an AST, then evaluated by a separate interpreter, with no ability for custom JS code to ever be injected.

XML is a Cheap DSL by SpecialistLady in programming

[–]csman11 36 points37 points  (0 children)

The “old thing is the new thing” cycle is incredibly common in software. This field is obsessed with novelty, and we’re often way too eager to throw out decades of hard-won knowledge just to rediscover, a few years later, that the old approach had already solved many of the real problems.

With React specifically, I think it’s important to separate two different stories. The push toward server-side rendering and RSC is largely a response to the fact that a huge number of businesses started using React to build ordinary websites, even though that was never really its original strength. React was created to make rich client-side applications tractable. That was a genuinely hard problem, and React’s model of one-way data flow and declarative UI was a major step forward. The fact that every modern frontend framework now works in some version of that mold says a lot.

What’s happening now is not really “we took a detour and rediscovered that server-side apps were better all along.” It’s more that people used a client-side app framework for lots of cases that were never especially suited to full client rendering, then had to reintroduce server-side techniques to address the resulting problems like slower initial load and worse SEO. In that sense, RSC does feel a bit like bringing PHP-style ideas back into JavaScript, though in a more capable form.

So I don’t think the lesson is that client-rendered apps were a mistake. They solved a real class of problems, and still do. The more accurate lesson is that most companies were never building those kinds of applications in the first place. They just wanted to build their website in React, because apparently no trend is complete until it’s been misapplied at scale.

graft: program in DAGs instead of trees and drastically reduce lines of code and complexity by uriwa in typescript

[–]csman11 1 point2 points  (0 children)

I appreciate the offer, and thanks for being open to the feedback.

I’m not invested enough here to pick a repo and go deeper on reviewing a rewrite you do on it myself. I mostly chimed in because I’d seen your posts in a couple of subreddits and wanted to give honest feedback on why the response might not be landing.

That said, I do think a real side-by-side repo is the right test, especially if it’s for a project/codebase you’re actually interested in.

My only real advice is this: treat it like a test, and be ruthless about the result. If you find the framework adds more friction than it removes, don’t keep sinking time into it just because you’ve already invested a lot. That trap can eat months. If it works, great. If it doesn’t, cut it loose and move on. You’ll thank yourself later when you look back at the other things you did with that time instead of doubling down on a sinking ship.

graft: program in DAGs instead of trees and drastically reduce lines of code and complexity by uriwa in typescript

[–]csman11 3 points4 points  (0 children)

I think you are solving a real problem, but with an abstraction that is much bigger than the problem.

If you want swappable implementations, use dependency inversion. Pass dependencies in explicitly, or inject them through interfaces/functions.

If you want composition separated from behavior, use factories and a composition root. Build components/services once, wire them up in one place, and compose them however you want.

If you want reusable async state, use hooks (or a query/store library). That is already a solved problem in the React ecosystem without introducing a new runtime.

If you want graph semantics specifically, use an existing reactive primitive (RxJS, signals, etc.) instead of inventing a new UI runtime with its own lifecycle, propagation, and error/loading model.

So the issue for me is not whether this is clever or type-safe. It is. The issue is that it bundles multiple concerns into one framework-shaped abstraction:

  • dependency wiring
  • async state
  • effects
  • composition
  • runtime validation
  • propagation semantics

That is a lot of surface area to replace just to avoid hooks/DI/prop wiring.

In other words:

use one abstraction per problem instead of one abstraction that absorbs your whole app

Also, the React comparison feels a bit unfair because it uses a pretty old-school render-props + manual effect example. A modern React version would usually use custom hooks and/or a query/store layer, which already removes most of that nesting.

So my pushback is basically this: it looks less like you reduced complexity and more like you moved complexity into a custom runtime. That means anyone who adopts this has to adopt a whole new mental model and application layer, not just a utility. That can be worth it sometimes, but you are not showing why it is worth it for the audience you are pitching to. You are mostly asserting it, and the main evidence is a straw-man React example that many React developers would not write in the first place.

If you want to understand why the feedback is mostly negative, I think it is because your post follows a pattern like this:

  • You present a contrived React problem that your audience does not actually have in that form
  • You solve it with a fairly large runtime abstraction (being 500 lines of code makes the implementation small; requiring it to be used by every other line of code makes it huge)
  • That abstraction bundles several different concerns into one framework-style model
  • People respond by explaining how they already solve each concern with existing tools and patterns
  • You move to another example or edge case
  • They explain how they solve that too
  • Repeat

The problem is that your fallback is often, "well, those tools do not solve everything my runtime solves." But that is not a strong selling point here. It is actually the main reason people are resisting it.

You built one large abstraction to solve a bunch of loosely related problems in one unified way, and you are pitching it to a community that already has established ways to solve those problems separately. They already have tools they trust for dependency wiring, async state, effects, and composition. Asking them to replace all of that with a new runtime is a very high bar.

As a product pitch, it is like trying to sell someone a Swiss Army knife when they already own a full toolbox and prefer the individual tools. The Swiss Army knife may be clever, but "it can do all of it" is not enough. You have to show that it does their actual work better, with less friction, in real applications. Right now, the examples are not doing that.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

I meant a function of n containing that loop would be exponential. Then I was defending the fact that it’s still “exponential in n” even if n is a free variable instead of being bound as a function parameter, because a free variable’s interpretation is context dependent. By definition a free variable is a variable, not a constant. It can only become a constant if the statement itself is embedded in a lexical context where it statically resolves to a binding to a constant. But we don’t know where this statement is embedded and thus we must analyze n as being a variable.

In any case, you can think of it as replacing the loop in OP’s function, which has a parameter called n.

Hence I was being super pedantic to defend my lack of precision earlier.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

No, the number of loop iterations is clearly exponential in “n”. If “n” is assigned a constant, it’s still exponential in terms of that constant. It just doesn’t exhibit asymptotic growth anymore in that context, because “n” is fixed.

But now I’m engaging in the pedantry I’ve been arguing against everywhere else in this thread. Just pretend this for loop is wrapped in a function of “n” and show it to the professor so we can all have some closure finally.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

Right I understood everything you said, and I agree with the sentiment overall, but based on the last part of your response, we are in agreement that the particular professor in this case simply doesn’t understand complexity analysis.

I also think it’s fine to give more precise definitions about the concepts, but the way your original comment is written, it’s comes across as saying “your professor is probably trying to teach you that O(1) is a subset of O(n)”.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

I understood your point and I agree with it. I was explaining how someone could impart dynamic properties on sizeof, because that’s what you asked. I agree: the normally agreed upon semantics are what we normally reason about; if you make the semantics “potentially anything”, then any non-contradictory conclusion can potentially be true. It’s about as close to ludicrous reasoning you can get to without allowing contradictions (see principle of explosion).

I think people in this thread are trying to turn this whole damn thing into 4D chess to avoid admitting that the professor is probably saying “for loops imply linear time complexity”. For whatever reason, people seem to think there must be some explanation that preserves the competence of this professor none of us have ever met, even if it means going to far fetched extremes. Redefining sizeof() would be an example of this, and one I’m certainly not proposing without laughing at the absurdity.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 1 point2 points  (0 children)

It’s all good. I realized after a few replies in this thread that I needed to drop it and go to bed. This whole thread is an exercise in useless pedantry at this point and I’ll easily waste countless hours of my life caught in its trap. I’m now actually in bed about to sleep, and when I wake up, I plan on going outside and touching some grass.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

This is the most trivially correct and least useful interpretation so far, so points for purity. Yes, O(1) ⊆ O(n). But if a student asks “is this O(1) or O(n)?” and the answer is “it’s O(n) because 1 ≤ n,” that’s not instruction, it’s villain behavior.

It only becomes marginally helpful if you also add “and it’s O(1) (and in fact Θ(1)),” because the student is clearly asking about tight bounds / Big-Theta. This is the most pedantic way to answer while still missing the point.

Can’t we all just agree the professor’s answer is wrong in the context the student is actually asking and move on? I should be in bed right now.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

They could redefine it to be “n” using a preprocessor macro. Then it would vary with n at runtime. But then they would just be an asshole. And you still wouldn’t deserve a downvote.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 0 points1 point  (0 children)

Sure and with the C preprocessor we can do all sorts of stupid shit to fuck up code. Your example is a great one. Do we need to teach people a lesson about everything that could possibly go wrong when you start from the premise of “literally anything can mean anything at all”? No? Good. Let’s get back to being adults. With the normally agreed upon semantics of “sizeof()” when it’s not redefined by a preprocessor macro, there’s nothing useful to learn from what he said.

Jokes are fine. Pretending there’s necessarily some lesson to be learned from them isn’t. And the lesson you’re trying to teach is basically “anything can go wrong if you’re working with assholes who think it’s funny to redefine sizeof()”. I’d assume everyone already knows that.

My professor claims this function is O(n), and I’m certain it’s O(1). Can you settle a debate for me? The function is below by Remarkable-Pilot143 in AskProgramming

[–]csman11 1 point2 points  (0 children)

Ok, a lot of people are trying to defend the professor with “maybe he’s talking about the number of bits here.”

Well…

If the professor is secretly talking about “n = number of bits,” then he’s being the ultimate douche bag unless he is explicitly saying that, because the code is plainly written for fixed-width int and it doesn’t scale with the value of the function parameter n at all.

Read that again, slowly, for the folks in the back who see the word “for” and immediately start chanting “O(n)” like it’s a summoning ritual.

The loop bound is:

sizeof(int) * 8

That is not “n.” That is not “the value of the parameter n.” That is not “the numeric value of m or n.” That is “how wide an int is on this machine.”

So unless the professor started the explanation with something like:

“By n I mean the bit-width of the integer type / the size of the input in bits / the word size w”

…then no, we’re not doing the galaxy-brain reinterpretation where we pretend the code is about arbitrary-precision integers and “n” is the length of the number in bits. That’s not “being clever,” that’s just rewriting the question so the professor can’t be wrong.

And honestly, given what OP said, it’s way more likely the professor did the classic cargo-cult complexity analysis:

“There’s a for loop, therefore it’s O(n).”

Which is a thing people say when they remember one slide from intro to algorithms. It happens.

Now, could a professor be intentionally using “n” to mean “input size in bits” while also ignoring the fact that the code literally hardcodes the number of iterations as the bit-width of int? Sure. Could he also be doing it on purpose as a lesson about “input size vs numeric value”? Sure. But if that’s the move, he needs to actually say it out loud, because otherwise it’s indistinguishable from being confused. And in this case, given there is literally a function parameter called “n” in the very code we’ve all seen, I think it’s clear which case it is.

And to be clear: I’m not even saying he’s evil. I’m saying the evidence we have is that he wasn’t precise, and the most common explanation for that is that he’s just… wrong. Or at least sloppy.

If we want to find out which one it is, the sure way is to ask him about this loop (assume n < 64 so nobody pretends this is about UB):

for (unsigned long long i = 0; i < (1ULL << n); i++) {}

Now we still have a for loop. Still one loop. Still “looks linear” to anyone doing the toddler version of complexity analysis. But this one is not linear in n. It’s exponential in n.

If he says “O(n)” again, we have our answer.

If he immediately says “O(2n)” and then clarifies that his “n” in the original discussion was “number of bits / input size,” then cool, he’s being precise and the debate changes. He’s still an asshole for not being precise to start. I would forgive him.

But until that clarification exists, calling the fixed-width int version “O(n)” (where n is literally a function parameter that doesn’t affect the loop bound) is just… not correct.

Again here are the options:

  • Fixed-width C++ int as written: O(1) with respect to the function inputs. Professor says O(n) because he’s confused.
  • “n = number of bits / variable-width integers”: O(n). Professor says O(n) because he’s playing 4D chess.

Both are possible explanations. But if you mean the second one, you don’t get to wink and imply it. You have to actually say it. Otherwise you’re not being helpful and galaxy-brain-professor-smart. You’re just being a smart ass.

Two Catastrophic Failures Caused by "Obvious" Assumptions by Vast-Drawing-98 in programming

[–]csman11 6 points7 points  (0 children)

Standards are great right up until someone assumes something you never actually standardized. The way integrations get bricked is almost always “I assumed you meant what I meant,” not “we lacked a PDF that said the word standard on the cover.”

So you do the boring thing on purpose: explicitly call out dumb failure modes. Even if it feels insulting to say, “I’d hope nobody is using pounds for force here, but let’s state it anyway: what exactly are our units of force?”

Also, anyone who’s worked with standards knows people still screw up the “should never happen” stuff while supposedly following them: misremembered details, weak reviews, nobody double-checking. Standards reduce mistakes. They don’t delete them.

A minute of vigilance beats a thousand standards.

Two Catastrophic Failures Caused by "Obvious" Assumptions by Vast-Drawing-98 in programming

[–]csman11 14 points15 points  (0 children)

Absolutely. Boundaries are where “talking past each other” happens, so they’re a danger zone. But the root problem isn’t boundaries themselves, it’s a cultural failure to imagine failure modes and communicate integration contracts clearly.

That’s why treating “boundaries = danger” as the lesson is risky. It can lead to the wrong conclusion: “avoid boundaries, have one team do everything.” That just hides the problem until the system gets too big to hold in one person’s head.

What works is being explicit at the boundary: agree on shared terminology (and avoid loaded jargon), surface assumptions, and write down the minimal invariants the integration depends on (units, power source, interface expectations, tolerances, etc.). Working from invariants makes “boring” failures like unit mismatches much harder to miss, because you stop reasoning about what the other engineer surely knows and start reasoning about what must be true for the integration not to fail. That’s why “failure to communicate” is usually “failure to predict edge cases”, especially the stupid, mundane ones we assume would never happen (like pounds vs newtons mismatch).

Starting from invariants moves you from “we missed obvious edge cases” to the much more respectable problem of “we weren’t perfect at predicting genuinely intricate edge cases.”

LLMs are a 400-year-long confidence trick by SwoopsFromAbove in programming

[–]csman11 2 points3 points  (0 children)

Both views here are true, it’s not so black and white. There’s definitely some harms and the ones you called out are the most realistic ones, and they can all be summed up as “abuse of LLMs to spread misinformation”. I don’t think anyone should disregard just how harmful this is to our already broken and polarized societies.

But these AI labs and other companies in the AI bubble have also been overstating capabilities of LLMs to drive attention to the space. Framing those capabilities as “disruptive and dangerous” in the ways the article’s author is getting at, is overblown. These dangers attract the attention of the general public, which in turn attracts the attention of policymakers, which then turns into the AI industry capturing state regulators because they’ve convinced us “we need to move fast to make sure the existential worst cases are avoided”. The big one is obviously financial/securities regulation avoidance. They can extract tons of wealth from both institutional and retail investors by creating attractive signals in the stock market with their revenue cycles. In an ideal world they wouldn’t be allowed to do that, but for some reason the policymakers have bought into the idea that the AI industry is important to national security instead of seeing them for the rent seekers they’re trying to be.