Using union types together with literal singleton types
See original GitHub issueThis is a follow up on a gitter conversation with @smarter
I’m getting accustomed to some very handy uses of union types and literal singleton types in the Typescript part of our application. I’ve been trying this and some other TS uses of union types my beloved Scala with Dotty.
Things don’t seem to work quite yet.
The most useful one is using union types with literal singleton types.
At the moment that combination doesn’t seem to work very much.
That’s a pity because there’s at least one use case that’s very useful. It’s possible to encode enumerations to constrain function arguments and assignments.
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 // seems to be interpreted as type Int
val digits: List[Digit] = List(-1, 0, 11) // this compiles right now.
It’s something that has just appeared in Typescript 2.0 and it’s really really useful.
I understood from @smarter that there’s a concern around the performance of inferring return types:
The main issue is that you can’t always preserve singleton types and unions, otherwise any complex if/else if/…/else will end up having a type like 10 | 32 | 50 | …, doing subtyping checks on huge union types like this is likely to slow down the compiler my idea is that we should preserve unions of singleton types and not widen them only if the expected type is a union
Another feature I use a lot in Typescript is working with non nullable types.
Instead of using an Option[A]
you use a A | Null
kind construction. Using a compiler flag or language feature import it would be possible to disallow null asignment.
val foo: String = null // wouldn't compile
val bar: String | Null = null // would compile
Currently the Null
type seems to be thrown away.
Here’s the relevant TS doc
I think it’s cool to have an alternative for the Option monad. But if it’s hard to implement I’m perfectly fine with using Option[A]
.
Invoking a function on an object with a union type doesn’t work right now with Dotty
class Bird() {
def fly()={}
def layEggs()={}
}
class Fish() {
def swim()={}
def layEggs()={}
}
def getSmallPet(): Fish | Bird = if(new Random().nextBoolean) new Fish() else new Bird()
val pet = getSmallPet() // returns Object right now
pet.layEggs() // This should compile shouldn't it?
pet.swim(); // and this shouldn't
The example is from Typescript docs
I guess this would be mostly useful for Scalajs since its compile target is very duck typing oriented.
For me and the Scala developers I’ve spoken to its the combination of union types and literal singleton types that’s most valuable.
Btw I was amazed how quickly I got a response on gitter. On a Sunday! 😃
Issue Analytics
- State:
- Created 7 years ago
- Reactions:1
- Comments:7 (3 by maintainers)
Top GitHub Comments
Null
is a subtype of every non-value class, non-nullable types are on our roadmap but require a substantial amount of work, especially if you want smooth interoperability with Java/Scala2val pet = getSmallPet()
withval pet: Fish | Bird = getSmallPet()
or alternatively if you putimport scala.language.keepUnions
at the top of your file (note: I’m pretty sure this should beimport dotty.language.keepUnions
, we seem to have a bug here). Currently we never infer union types without thekeepUnions
import but your example illustrates a case where we might want to relax this rule, this might be hard to do since we would need to precisely keep track of the source of the inferred type.Fixed by #6299 🎉