upvote
IME, this is a good thing.

The problem with ad-hoc unions is that without discipline, it invariably ends in a mess that is very, very hard to wrap your head around and often requires digging through several layers to understand the source types.

In TS codebases with heavy usage of utility types like `Pick`, `Omit`, or ad-hoc return types, it is often exceedingly difficult to know how to correctly work with a shape once you get closer to the boundary of the application (e.g. API or database interface since shapes must "materialize" at these layers). Where does this property come from? How do I get this value? I end up having to trace through several layers to understand how the shape I'm holding came to be because there's no discrete type to jump to.

This tends to lead to another behavior which is lack of documentation because there's no discrete type to attach documentation to; there's a "behavioral slop trigger" that happens with ad-hoc types, in my experience. The more it gets used, the more it gets abused, the harder it is to understand the intent of the data structures because much of the intent is now ad-hoc and lacking in forethought because (by its nature) it removes the requirement of forethought.

    "I am here.  I need this additional field or this additional type.  I'll just add it."
This creates a kind of "type spaghetti" that makes code reuse very difficult.

So even when I write TS and I have the option of using ad-hoc types and utility types, I almost always explicitly define the type. Same with types for props in React, Vue, etc; it is almost always better to just explicitly define the type, IME. You will thank yourself later; other devs will thank you.

reply
Yeah, Typescript feels like it had has arrived at the point where someone needs to write “Typescript: the good parts” and explains all of the parts of the language you probably shouldn’t be using.
reply
> ad-hoc unions (on the fly from existing types) that are possible in F#

Are you sure? This is a feature of OCaml but not F# IIUIR

Edit: https://github.com/fsharp/fslang-suggestions/issues/538

reply
> It doesn't cover ad-hoc unions

Yes and no. C# unions aren’t sealed types, that’s a separate feature. But they are strictly nominal - they must be formally declared:

    union Foo(Bar, Baz);
Which isn’t at all the same as saying:

    Bar | Baz
It is the same as the night and day difference between tuples and nominal records.
reply
Hi there! One of the C# language designers here, working on unions.

We're very interesting in this space. And we're referring to it as, unsurprisingly, 'anonymous unions' (since the ones we're delivering in C#15 are 'nominal' ones).

An unfortunate aspect of lang design is that if you do something in one version, and not another, that people think you don't want the other (not saying you think that! but some do :)). That's definitely not the case. We just like to break things over many versions so we can get the time to see how people feel about things and where are limited resources can be spent best next. We have wanted to explore the entire space of unions for a long time. Nominal unions. Anonymous unions. Discriminated unions. It's all of interest to us :)

reply
it's basically `union <name>([<type>],*)`, i.e.

=> named sum type implicitly tagged by it's variant types

but not "sealed", as in no artificial constraints like that the variant types need to be defined in the "same place" or "as variant type", they can be arbitrary nameable types

reply
Third paragraph from the top:

> unions enable designs that traditional hierarchies can’t express, composing any combination of existing types into a single, compiler-verified contract.

reply
It's very unclear which you mean by that.

To me that "compiler-verified" maps to "sealed", not "on the fly". Probably.

Their example is:

public union Pet(Cat, Dog, Bird);

Pet pet = new Cat("Whiskers");

- the union type is declared upfront, as is usually the case in c#. And the types that it contains are a fixed set in that declaration. Meaning "sealed" ?

reply
OK then, what is the opposite of this, the adhoc union?
reply
I don’t know for sure, but I’m guessing something like

(Dog, Cat) pet = new Cat();

So without defining the union with an explicit name beforehand.

reply
Well, you can do this in c#:

  var someUser = new { Name = "SideburnsOfDoom", CommentValue = 3 };

What type is `someUser` ? Not one that you can reference by name in code, it is "anonymous" in that regard. But the compiler knows the type.

A type can be given at compile-time in a declaration, or generated at compile-time by the compiler like this. But it is still "Compiler-verified" and not ad-hoc or at runtime.

the type (Dog, Cat) pet seems similar, it's known at compile-time and won't change. A type without a usable name is still a type.

Is this "ad-hoc"? It depends entirely on what you mean by that.

reply
I don't follow the question. Maybe define the term that you are using?
reply
Top comment mentioned the term without defining it, confusing me and seemingly most of the thread: https://news.ycombinator.com/item?id=47649817
reply
I mean that Cat, Dog and Bird don't have to inherit from the union, you can declare a union of completely random types, as opposed to saying "Animal has three subtypes, no more, no less", which is what F# does more or less.
reply
I'm pretty sure at one point there was proposal that allowed declaring something like `int or string`. Not sure what happened with it though.
reply