Remix.run Logo
shadowgovt 4 days ago

This points to a software best-practice: "Don't leak types from your dependencies." If your package depends on A, never emit one of A's structs.

Good luck finding a project of any complexity that manages to adhere to that kind of design sensibility religiously.

(I think the only language I've ever used that provided top-level support for recognizing that complexity was SML/NJ, and it's been so long that I don't remember exactly how it was done... Modules could take parameters so at the top level you could pass to each module what submodule it would be using, and only then could the module emit types originating from the submodule because the passing-in "app code" had visibility on the submodule to comprehend those types. It was... Exactly as un-ergonomic as you think. A real nightmare. "Turn your brain around backwards" kind of software architecting.)

deredede 4 days ago | parent [-]

I can think of plenty situations where you really want to use the dependency's types though. For instance the dependency provides some sort of data structure and you have one library that produces said data structure and a separate library that consumes it.

What you're describing with SML functors is essentially dependency injection I think; it's a good thing to have in the toolbox but not a universal solution either. (I do like functors for dependency injection, much more than the inscrutable goo it tends to be in OOP languages anyways)

shadowgovt 4 days ago | parent [-]

I can think of those situations too, and in practice this is done all the time (by everyone I know, including me).

In theory... None of us should be doing it. Emitting raw underlying structures from a dependency coupled with ranged versioning means part of your API is under-specified; "And this function returns an argument, the type of which is whatever this third-party that we don't directly communicate with says the type is." That's hard to code against in the general case (but it works out often enough in the specific case that I think it's safe to do 95-ish percent of the time).

int_19h 4 days ago | parent [-]

It works just fine in C land because modifying a struct in any way is an ABI breaking change, so in practice any struct type that is exported has to be automatically deemed frozen (except for major version upgrades where compat is explicitly not a goal).

Alternatively, it's a pointer to an opaque data structure. But then that fact (that it's a pointer) is frozen.

Either way, you can rely on dependencies not just pulling the rug from under you.

shadowgovt 4 days ago | parent [-]

I like this answer. "It works just fine in C land because this is a completely impossible story in C land."

(I remember, ages ago, trying to wrap my head around Component Object Model. It took me awhile to grasp it in the abstract because, I finally realized, it was trying to solve a problem I'd never needed to solve before: ABI compatibility across closed-source binaries with different compilation architectures).