Hacker News new | ask | show | jobs
by bjz_ 2341 days ago
It's probably more that most mainstream typed languages, like C, C++, Java, C#, etc. have mainly gone down the nominal route, as opposed to languages like Standard ML and Typescript that tend to be more structural.

Structural vs. nominal typing is pretty well known in programming language design circles, but circulating that knowledge is a constant uphill challenge. One of the reasons many of us are frustrated with Go's designers were how quickly they threw this work under the bus as part of their initial marketing strategy, which kind of knocks away the ladder for anyone else who is curious to learn about this stuff.

1 comments

> languages like Standard ML and Typescript that tend to be more structural.

AFAIK SML is nominal. OCaml has a structural subsystem in that its object system is structural. The vast majority of the language is nominally typed.

> It's probably more that most mainstream typed languages, like C, C++, Java, C#, etc. have mainly gone down the nominal route

It's not just mainstream typed languages. Almost all statically typed languages use nominative typing. Tt doesn't have any real drawback and it's just simpler to understand and work with.

A language like typescript would go with a structural system because it's a much easier bridge and sell from an object-oriented dynamically typed world.

> AFAIK SML is nominal

I guess I was referring to how records are structural. Although granted tagged unions are nominal, and you can get nominal typing through modules. I was probably wrong in posing it as 'one or the other' - seeing as many languages have a mix of both. I'm definitely not saying that nominal typing is bad, it's just that it's nice to have the option to go structural if you want, and many have not known that this option exists.

Modules are structurally subtyped in SML. They are not nominally subtyped. You do give them names for readability and reuse, but the name is not important to the typechecking. Module types (signatures) are checked according to the types of the fields they have, with one module type being a subtype of another if it includes the other's fields. For instance, if I have a functor:

    functor F(S : sig val x : int end) = struct ... end 
then I can apply F to any module that includes a field x of type int. I don't even have to name the functor argument:

    structure Foo = F(struct val x = 3 val y = 4 end)
Unions, on the other hand, have nothing to do with subtyping. In the union

    datatype foo = Bar | Baz

    val foo1 = Bar
    val foo2 = Baz
There is no subtyping. There is exactly one type in question, which is "foo", and no other types to form a subtyping relationship.

You might be thinking instead about how some languages implement algebraic data types over a language like Java by replacing "foo" with a supertype and implementing the variants as subtypes. Scala and Kotlin are examples of this. Doing things this way allows you to do interesting things that are not possible in SML, such as typing for exactly the variant you expect.

Ocaml has always had, in addition to normal records, structurally typed records and polymorphic variants. It also has SML's structurally typed module system, though goes much further by allowing modules as runtime values.

Are records structural in Standard ML? What happens if you have multiple records with the same structure but different names? I thought structural typing of records was not very useful without row polymorphism.
Records of the same structure but different names are equivalent. In fact tuples are just sugar over records with numeric fields starting from 1. Note that this is not the same as in OCaml, afaik.
It depends. OCaml explicitly has polymorphic records as well.