I used to use it extensively in C++: beware though of (super long) compile errors while nesting them through templates params. :)
Edit: I was wrapping pointers for instance to not mix GPU and CPU memory addresses, to define a unidirectional flow of memory transfers between devices (and avoid manual synchronisation and tracking)... etc.
You could somehow define a state machine through types and ensure compositional coherence.
Using phantom types in state machine transitions is really cool, yeah. I came across a little gist that explored that idea in Elm [1] the other day, actually.
It's kind of amusing reading this as a Typescript dev because I'm so used to Typescript's expressiveness I couldn't really work out which trick it was trying to tell me was novel.
The answer is structural typing (aka duck typing).
Not quite. I guess in some ways phantom types achieve the opposite of structural typing.
In a language with structural typing, two types are considered equivalent if they share the same structure. With phantom types we can have a single structure and disambiguate between different uses without touching the underling structure. There's no difference between Id(User) and Id(Post) at runtime, the annotation is purely a compile-time restraint.
In fact, because of typescript's structural typing, the only way to use phantom types in ts is to have some dummy field of type `never` that the type system can use to disambiguate between the two [1].
Yes, exactly, it's because of Structural Typing that you have to implement Phantom Types in that hacky way rather than Structural Typing being the "trick".
https://stackoverflow.com/questions/3730019/why-not-use-doub...