Good take if you ask me. When I want to flesh out an ontology, I find that TypeScript's type syntax more natural than anything else. Generally I find it to do an amazing job of bridging between a very expressive and flexible type system and a language that at runtime knows nothing about your types.
Want structural/duck typing? No problem; that's the default. Define new interfaces that old classes happen to implement without having to wrap them. I always wanted that feature!
Want nominal types? No problem; just declare a field indicating the type name!
interface Square { classRef: "http://example.com/Shapes/Square"; width: number; }
interface Circle { classRef: "http://example.com/Shapes/Circle"; diameter: number; }
type Shape = Circle|Square;
(I like to express things in a way that can be easily translated to RDF, hence my use of URIs for type and attribute names)Since TypeScript types don't runtime checks, you can even do it on atomic types!
type USDollars = number & { [Symbol.for("http://ns.nuke24.net/Synx/unit")]: "USD" }
type CanadianDollars = number & { [Symbol.for("http://ns.nuke24.net/Synx/unit")]: "CAD" }
const someAmount : CanadianDollars = someExpressionReturningUsDollars; // Compile error! Try doing that in Java! I OFTEN WISH I COULD