HNHacker News
TopNewBestAskShowJobs

sirwhinesalot

3,266 karma · joined August 7, 2021

submissionscomments
sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
There are various ways to solve the problem (if you consider it a problem, I personally don't, there's no need for things to be this extremely decoupled, but for the sake of argument):

1) Allow objects to send messages to an implicit "parent" object, which supports "doesNotUnderstand" ala Smalltalk and that by default also forwards to the implicit parent. That means you can implement "log(...)" at the right point in the abstraction without manually passing a logger around.

2) Use algebraic effects, no need to handle this with OOP. A function requests the "log" effect and a handler defined in a higher level scope takes care of implementing log.

3) Have a language with an implicit context parameter that allows putting arbitrary things in it (closest would be Odin but Odin has a fixed set of operations in the context). Because it is implicit in the calling convention methods can be oblivious to it.

4) You separate the concerns: logging is the act of recording some information. Where that information is ultimately sent or stored is a separate concern. This is the more functional view of the problem. If you follow a "model-view-update" approach like the so called "Elm Architecture", logging is just another task produced by the view function, it doesn't need any special handling.

It's not that this is a "problem" of OOP, it is that OOP doesn't give you any tools for tackling this problem. That is why dependency injection frameworks exist. Some other paradigms and non-OOP architectures don't have any trouble with it. You can also replicate those architectures in an OOP language, but that's still "contorting the paradigm" rather than it helping or guiding you towards a clean solution.

Personally I think a global singleton logger is perfectly reasonable, it is other people (the ones using DI frameworks) that think otherwise ;)

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
No it is not a fault of OOP, it is just that OOP does not really provide the tools to help either. It is the fault of people with the wrong expectations or of those trying to sell snake oil.

OOP allows to create decoupled systems but it doesn't help in creating them. It is not an inherent property of the paradigm, but many people sold it as such.

"Use OOP and your application is now magically more modular! Buy my latest book to find out how!"

Since you mentioned you are a junior I guess this sort of nonsense is from before your time. No worries, that just means you won't have stupid dogma shoved down your throat. Use whatever paradigm if it helps you and avoid whatever gets in the way.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
I think dogma is far worse than "tight coupling" or introducing mutation or even inheritance when it is the simplest and most direct solution to a problem. People get too hung up on the "ideal" way to do things.

I can criticize a paradigm while also using it anywhere and everywhere it benefits me ;)

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
It's not really true that components in physical engineering domains have no memory. The behavior of many physical components can only be described with access to a recent history. Other components, specially controllers, are typically state machines (which obviously have state).

But it is true that there will always be parts of a software system that are highly coupled. I'm not even sure if that's even a problem, unless the coupling is also highly tangled (meaning the connections are coming from too many places).

But if you want to decrease coupling between parts of a system, OOP by itself is not particularly good at it. Even something like an Entity Component System is usually less coupled than most OOP codebases because while each System is heavily coupled to the components they do work on, the Systems are usually fully independent from one another and easily replaceable.

sirwhinesalot··on Parse, Don't Validate – In a Language That Doesn't Want You To
Languages with refinement types (or contracts) like Dafny and Liquid Haskell can typically handle numerical predicates directly. Some can even handle string predicates directly, including regular expressions. They also allow you to write complex predicates as separate functions, albeit with limited expressiveness.

But you hit performance and/or outright computational limits (halting problem) rather quickly.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
To be clear I'm not arguing against objects here, I don't disagree with anything you wrote. I'm only arguing against the somewhat commonly held idea that OOP helps with loose coupling.

Interfaces (a feature of OOP) can be used to decrease coupling, but the paradigm itself doesn't really provide any assistance in regards to coupling.

The default is for objects to directly instantiate other concrete objects and to be able to directly communicate with any object they at any point come in contact with. That is strong coupling, by default.

You have to do extra work to get looser coupling. Smalltalk style OO is much better (regarding coupling) because you don't have explicit interfaces, you can always replace an object with another. You can also query all live objects of a certain type and replace all of them with a proxy if you so wish. Way better.

But you can go even further in regards to loosening coupling. You can have objects communicate through a tuplespace (Linda), you can have objects receive and send messages only through ports, you can have objects send a message to their implicit parent who is then responsible for redirecting it.

Mainstream OOP is a very poor paradigm full of issues that gets mogged by everything. Composition-over-inheritance is an admission of defeat for a paradigm that lacks native delegation support (good on Kotlin for realising this).

It even gets mogged by languages like Haskell that introduced the more loosely coupled idea of typeclasses, which were inherited by Rust (traits), Swift (protocols), Go (interfaces, not to be confused with declaration-time interfaces as in Java).

We dug a suboptimal hole and stuck our head in there for 50 years.

sirwhinesalot··on Parse, Don't Validate – In a Language That Doesn't Want You To
The computer-science answer to this problem are called "refinement types", where you can attach arbitrary predicates to a type, e.g. (pseudo-code):

    fn send_birthday_mail(user: {u: User, u.birthday != null})
Contracts are a similar solution that restricts the predicates to only appearing in function types.

The difference between this and an assert is that it gets checked at compile time (it can get quite expensive to do the check though).

What can you do in mainstream languages? As much as is worth and no more than that. String -> User is worth it, User -> UserWithBirthday is not.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
It's crazy to me that Linda came out 40 years ago. I think our field is completely blind to what was achieved in the past.

Smalltalk is to me the most obvious case where people somehow don't realize we had a fully live environment, where the entire IDE (or really the whole operating system) could have open heart surgery done on it while it was running, in the late 1970s!

And that's a language that is at least somewhat in the public consciousness, languages like Linda? Nobody knows them. Synchronous languages like Lustre and Esterel? Some engineers know them (and pay good money for them), but not software engineers.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
Yes, for example if you are doing any sort of mechatronic systems engineering (cars, planes, rockets, etc.), you're typically using tools like Simcenter Amesim or Modelica or Simulink/Stateflow to digitally prototype the system.

These tools are all oriented around blocks with ports. They receive data on their input ports and emit data on their output ports. They do not know where that data is coming from nor where it is going. They cannot instantiate other blocks.

To create a new block you either create a primitive one (described directly in terms of differential equations, since these are physical simulations, or as actual code, be it C or MATLAB or whatever else), or you connect blocks together to form a larger block.

This creates a strict hierarchy, connections between blocks can only happen at the same level of abstraction. You can just rip out a component and replace it with another with the same ports, or with multiple components each filling in part of the job, or you can plop another component in the middle that's only used to process the data inbound and redirect it somewhere else.

This makes coupling extremely loose by design. The paradigm enforces it.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
Dependency Injection frameworks are a workaround for limitations of the paradigm, much like Design Patterns also are. They're necessary (by a very stretched definition of necessary) because people want to do certain things and the paradigm is fighting them.

Some part of the code wants to do logging. The paradigm-native solution is to just instantiate a logger object directly, but that's not really what you want to happen, because there are application-level concerns as to where those logging messages should go.

So instead of a direct new, you create a Logger interface (extra work), then use the Factory pattern (extra work) to hide the concrete logger objects that get instantiated. Ok, but now you are still creating multiple independent loggers, which will trample on each other.

So you employ the Singleton pattern (extra work), but now you can't use different loggers for different parts of the codebase.

Ok, let's instead have the application instantiate the proper loggers, and then thread them through method arguments as needed (extra work).

But now passing all those loggers around is super annoying and extremely noisy, so you create a framework that can inject them where they are needed.

Great, now you have a whole framework just to pass some logger objects around. The framework is not the problem per se, it exists for a reason, the problem is that someone felt the need for it. And it is a massive pile of complexity that is NOT worth it.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
The Alan Kay variant of OOP is superior in this regard, and I'd argue the Erlang variant even more so (Actors are just asynchronous objects).

But both of them are still suboptimal because they allow objects to instantiate one another and to directly communicate with whomever they come in contact with. That creates strong coupling and you have to "fight the paradigm" to avoid it.

Other engineering domains have this figured out: components can only send and receive data through ports, they are never aware of their siblings. Only a component at a higher level of abstraction can decide how the ports of lower level components connect to one another. That completely eliminates coupling.

(You can of course replicate this type of architecture with OOP, but you can also replicate it with any turing complete paradigm, that's neither here nor there)

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
I agree "high cohesion, loose coupling" is a good architectural property to strive for, but OOP is terrible at it and there's no reason to use it for this.

Trying to achieve "high cohesion, loose coupling" in OOP has led to the creation of (supposedly) best practice recommendations like the SOLID principles, and the development of monstrosities like dependency injection frameworks.

If OOP was actually good at "high cohesion, loose coupling", you wouldn't need them.

sirwhinesalot··on Alan Kay on the meaning of "object-oriented programming" (2003)
Another commenter pointed it out already but might as well stress the point: objects are bundles of data and behavior. Your example is missing data.
sirwhinesalot··on Linux eliminates the strncpy API after six years of work, 360 patches
I have in the past made fun of the Linux kernel devs, supposedly some of the best C developers in the world, for not knowing how to make stringbuffer and stringview types, but to be fair to them we didn't have the consensus we have today on the topic.

You know who did have the right idea though? Dennis Ritchie, who proposed a fat pointer type for C all the way back in 1990. Would have made for a perfect addition to C99. Imagine how different the world might have been had the committee added that in.

We had a second chance with the release of the "C's greatest mistake" blog article from Walter Bright in 2007, essentially pushing for the same idea as Ritchie (slices/stringviews) but explained with much clearer language.

Alas, didn't make it to C11.

We're now in C23, still nothing. But we did get _Generic and VLAs! Party hard.

sirwhinesalot··on Apple WWDC 2026
It didn't happen when they introduced Leopard, because the new design was legitimately better than the ugly brushed aluminium look that preceeded it.

When they toned down the horrific excesses of skeumorphism (like leather textures) on macOS but still retained some skeumorphic design (e.g., buttons that look like buttons) people were pretty ok with it too. I remember some complaints that the lickable aqua buttons were gone but that wasn't really a serious complaint, just more of a "I kind of liked those gaudy buttons, sad they're gone" type of sentiment.

sirwhinesalot··on Apple WWDC 2026
They fixed all the extremely egregious issues with Liquid Glass, good to see.

It's still an extremely ugly, "worst of both worlds" combination of wasted space (from early-gen flat design) with gaudy effects (from late-gen skeumorphism), but at least now it is usable.

I'd never update to macOS 26, but 27 I might, begrudgingly.

sirwhinesalot··on India's surprise baby bust
It wasn't just that. Speaking from both personal experience and from second-hand experience I got from my parents and grandparents, there was a very obvious shift that went beyond that.

My mother when she was younger used to live surrounded by extended family and they were always at each other's houses doing things together (I mean the adults, not just the kids, they'd help each other out).

I still grew up with extended family around, my grandparents, parents and uncle all lived right next to each other. I also had cousins nearby but already the shift had started, people were living more isolated.

But everyone still knew everyone and it was pretty safe to just let kids roam around.

That sort of life is gone.

sirwhinesalot··on India's surprise baby bust
I'm a father of 2. If not for my mother in law I would have gone insane by now. Way harder than 1. 1 was mainly hard because we had no idea what we were doing. With 1 it is pretty easy for a parent to take over while the other rests. Not so with 2 kids.

Dealing with a rowdy toddler and a puking newborn at the same time is draining in a way that is impossible to describe.

sirwhinesalot··on India's surprise baby bust
The real reason why nobody is having kids can be summarized by the sentence:

"It takes a village to raise a kid".

Look up how much time a mother actually used to spend holding their own child vs other people holding that same child. It was something like 40%.

When you lived surrounded by your tribe, you had an extremely strong support network that no longer exists in industrialized society.

Add to that all the other factors like:

- both parents working often without the grandparents nearby or even willing to help

- unnafordable housing, even when renting

- effectively delayed adulthood (university) into the 30s

- falling fertility from whatever chemicals we're poisoning ourselves with

- anti-natalist messaging

- etc.

sirwhinesalot··on Algebraic Effects for the Rest of Us
Yeah, there's three things you're supposed to implement: try/handle, perform, and resume. The names can vary (e.g., perform is often called "raise" or "do"). They have well defined interactions.

I don't actually know what the original paper describing what algebraic effects are supposed to do is, I just know them informally from Koka, Effekt, etc.

sirwhinesalot··on Algebraic Effects for the Rest of Us
I'm thinking a less formally than that. Protocol in a very layman-y "perform is supposed to do this, resume is supposed to do this".

For example, Koka compiles handlers differently depending if they do multi-shot continuations or not. It can do this because all that matters is "perform is supposed to do this, resume is supposed to do this", not what they turn into (same as "if" turning into "cmov" in certain cases). I think it uses a continuation-passing style sort of implementation, but I can't quite remember.

Daan's libhandler implements effects for C in an entirely different manner. It captures the stack much like my example or a stackful coroutine library would.

I'm sure there a formal definitions in both the koka papers and the libhandler paper, but I just skim that stuff ;)

sirwhinesalot··on Algebraic Effects for the Rest of Us
They're really just a protocol. You can implement them in various ways. They will always be some sort of delimited continuations but a "function call" or continuation passing style or anything of the sort does not have to be involved at all.

For example, let us say I don't allow "multi-shot" continuations like in your library, and I'm implementing Algebraic Effects in my own interpreted language.

One way I can implement effects and handlers is to have a handlers get registered in a stack, then when an effect is triggered, save the IP and current stackframe, search for the right handler and jump to it. "resume" then just resets the stackframe, pushes a value into the stack, and sets the saved IP.

(Only saw your edit after posting, sorry, but yes)

sirwhinesalot··on Algebraic Effects for the Rest of Us
I agree but I also think it's important to point out that Algebraic Effects typically refers both to a runtime feature (the try/handle delineated continuation stuff) and also a type system feature.

The latter is very important because in your example it would not really be hidden. If your function does not have the "exn" effect, it cannot call functions that throw exceptions, full stop. Same with any other effect including IO if you want.

Basically function coloring taken to the extreme. In a statically typed language with statically typed effects you actually cannot get surprised, which was your major complaint.

Type systems that support algebraic effects also typically support row polymorphic effects (fancy generics) so you can make a function generic over "color", avoiding the "function coloring" problem.

Now, having said that, why did I say I agree with you? Well because algebraic effects are a lousy user-facing feature. You almost never want to implement your own handlers, at best you'll plug in a custom handler for the IO effect and that's about it. (And for exceptions of course, but that's just exceptions with extra steps)

Where they shine is for the language implementer. They provide a framework on which exceptions, generators, async/await and even prolog-like backtracking can be implemented, while (very importantly) defining how they compose. That's really the bit that makes them so interesting from a research point of view and why they might make it into the mainstream languages, even if the language doesn't actually ever expose them for you to use.

sirwhinesalot··on Algebraic Effects for the Rest of Us
We disagree that they're just continuations (only one of many possible implementation strategies) but agree they're nothing special ;)
sirwhinesalot··on Algebraic Effects for the Rest of Us
Equating "algebraic effects" with "continuations" is like saying "if" is just "goto" (which isn't even true, e.g., an if can turn into a cmov or whatever).

The only mystique around algebraic effects is the same mystique there is around monads. I don't know if people have started equating algebraic effects to burritos yet but that's a pretty good way to take something simple and turn it into something confusing.

sirwhinesalot··on Native all the way, until you need text
NSLinkAttributeName?
sirwhinesalot··on Native all the way, until you need text
If you need to display HTML content (what Markdown usually translates to) then WKWebView is the control to use! Or use something like litehtml which should be more than enough for Markdown unless you want to support "Animated Gifs" (that are actually H.264 movies these days) or whatever else.

You can still use native controls for the rest of the UI and have 0 Javascript running. I'm not sure I understand what the problem with NSTextView was though. It's pretty performant as far as I can tell?

sirwhinesalot··on I'm going back to writing code by hand
This is actually what I do. I'm extremely picky about the code and force the LLM to rewrite it 1000x times until it is basically exactly what I want. You might be wondering what is the point when it would be faster for me to just write the code myself?

I have ADHD and for whatever reason telling the LLM what to do instead of doing it myself bypasses the task avoidance patterns and/or focus problems I tend to suffer from. I do not find it fun, but I am thankful for it.

sirwhinesalot··on If more than 50% press blue, everyone survives. Red pressers always survive
I love this thought experiment.

If you pick red you survive.

If you pick blue and at least 50% of people picked blue, you survive, otherwise you die.

There's 0 advantage to picking blue, none what so ever, the only reason you'd pick blue is because you assume there's some subset of people that is so unbelievably stupid that they'll pick blue. You're sacrificing yourself in the hope of saving them.

IMO, the reality is that everyone you think would pick blue would actually pick red. Very few people are that stupid, and even if they are they probably also have access to someone not as stupid who will tell them to press red.

The only people you'd be saving are other suicidal white knights that pick blue to save those imaginary "blue pressers", and the outcome of that, since that blue pressing base doesn't actually exist, is that you're all just committing collective suicide for absolutely no reason.

What "blue pressers vs red pressers" says about our society is best left to philosophers.

sirwhinesalot··on Microsoft offers buyouts up to 7% of US employees
There is good quality stuff at Microsoft, it's just on some of the innards.

NT Kernel, Direct3D, .NET Runtime. Also a lot of stuff that came out of Microsoft Research like Z3.

Which also happen to be the sort of projects older devs would normally be working on.

← PreviousPage 2 of 29Next →