oh yeah, other thing: you can still probably get benefits out of doing (Seal((A | B | C)) | B | D) instead of full types, such as your hidden tag being something like A/B1/C/B2/D instead of Seal/B/D where Seal unpacks to match tag A/B/C, but I guess you could also do that with full enums? More to write, though, and doing this either way still requires more inlining / monomorphization, but it's an optimization that could be neat for hot stuff

you probably never meant to mess with the inner type like that. this is solvable by introducing an explicit separator, something like (Seal((A | None)) | None), maybe with some custom syntax? but you want a default way to say that you're not normally walking inside that type, and I don't think it's too far to say that should be the default behavior—which introduces a new issue of how to make ((A | B) | C) be (A | B | C) by default in stuff like function bodies, but that's worth thinking about.

if you have a dedicated None type, then something like a fallible getter of ints returning (U8 | None) seems obvious. extending that, if you want to communicate how it fails, doing (U8 | ErrFoo | ErrBar) looks nice and is easy to extend or reduce later. However, you change that U8 to a generic type var (e.g. A), and now you've introduced a function that's fine ~as long as~ you never use ErrFoo / ErrBar / None as a union component for A. The former two are reasonable, but the latter feels *bad*

what bugs me the most about languages that emphasize anonymous flattened sum types (your value is a member of a set of types that you can extend, and the details for that are managed for you behind the scenes) is that they seem to fly in the face of doing the obvious thing as complexity naturally grows.

The fact that I just now stumbled upon explicit wrapping solving some sum problems seems like a bad sign—what's so bad about Rust/Haskell style algebraic data types, and why wouldn't the language docs mention a way to get around potential issues like this at all?

maybe I'll just go focus on Elixir instead and let Pony mature some more before coming back, but I really do want this to be a good language

I feel like the answer is "make a clearer API", but if I'm stuck expressing multiple types of failure, what do I do? the stark simplicity seems counter to a lot of the rest of the language that aims for clear and concise actor code that lets you really do what you need

you could probably hack together your own Rust-like tagged union, but now your union involves (A | B) sums, and you're screwed unless you introduce an explicit wrapping maybe?

it's like null pointer handling again, but reinvented with a bit more fancy type handling behind the scenes

I don't like this, and am hoping that at some point I discover that I'm just looking at it wrong, but stuff like collection/HashMap's apply lookup erroring on keys not being present and Iterator's general design isn't looking too good

I'm also not sure how I feel about the prevalence of anonymous sum types all over the places over more traditional compositional stuff like Option

on the one hand, it's simple to quickly slap together a set of whatever and match on it, but on the other hand, it seems you have to prefer error / ? to show that you have no value instead of doing an Option or (… | None) because your generic type could be (A | None) and the set union there would eat your None, and weh