#Why does Gleam does not have subtypes or unions?

1 messages · Page 1 of 1 (latest)

light sparrow
#

Just for curiosity, thanks 🙂

vapid gale
#

subtyping is largely a feature of object-oriented languages, of which gleam is not one. We could have structural subtyping but louis decided against this to encourage clear nominal types.

wrt unions, they require runtime type checks and/or fairly sophisticated flow analysis to statically type them. the erlang and elixir communities have already soft bought into the idea of tagged unions through heavy use of tuples with atoms as their first element, eg {:ok, 1} this maps neatly to gleam's custom types: using untagged unions would just mean everyone re-invents the wheel to follow the patterns that are already idiomatic (eg see typescript and everyone doing type Thing = { tag: 'Foo', ... } | { tag: 'Bar', ... })

#

one of gleam's strengths, imo, is that its simple both in semantics and in implementation - some things become harder or more awkward to express as a consequence but i think the trade-off is worth it

autumn palm
#

Additionally:

I think subtypes make languages a lot harder to use. Concepts like covariance and contravariance are required knowledge yet they are complicated and easy to misunderstand. Error messages become much less clear. HM type checking is no longer really possible. Overall it would not contribute to the experience we’re looking to build, we would likely instead have a much less satisfying experience like Java or TypeScript.

Untagged unions in reality mean that your runtime tags everything for you. In Java that is the case, so you may as well take advantage of that cost you have already paid. On Erlang it is done only so much as you know what primitive type something is, so we only know a tuple is a tuple and a reference is a reference. The problem here is that multiple types have the same runtime representation! A timer reference has the same runtime representation as a monitor reference, we cannot tell them apart. This leaves us with these options:

  1. Dramatically reduce the precision of the type system by making is structural, and remove the ability to build nice typed APIs over these ones. Gleam programming becomes much more confusing and error prone.
  2. Start tagging everything. Gleam code becomes slower and FFI becomes very difficult. Using Gleam from other languages practically becomes impossible.
vapid gale
#

Start tagging everything.
is that not what the compiler does today anyway?

autumn palm
#

Compile time yes, runtime no

#

An int is just an int at runtime

#

Is it currency, a database id, or a timeout? Dunno

vapid gale
#

well sure but if you introduce new gleam types to disambiguate between those

type Currency { Currency(Int) }
type DatabaseId { Id(Int) }
type Timeout { Timeout(Int) }

at runtime aren't these {:currency, int} {:id, int} and so on?

autumn palm
#

They are

#

I’m talking about external types for ints there

#

For non external types the problem remains for tuples and atoms

#

Is it a tuple with an atom in the first position or a record? No way to tell

#

Option 3: only permit unions with structurally distinct types. I think this would be a very frustrating and confusing experience

vapid gale
#

yeah that'd suck

autumn palm
#

Or reading

#

That seems more likely

vapid gale
#

neither 😮 im just slowly absorbing the syntax through osmosis here

foggy charm
#

doesn't TS have the same problem though? numbers are just numbers etc. but it allows for number | string and stuff and if you stay completely within TS then you can trust it's correct.

autumn palm
#

It doesn’t because all types in TS have unique runtime representations

#

And also TS does not provide the UX I am aiming for with Gleam. It’s very complicated and far from as helpful a compiler

meager depot
vapid gale
#

i dont hear many people complaining about ts really

meager depot