Skip to content

Using union types together with literal singleton types #1495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
joost-de-vries opened this issue Sep 5, 2016 · 7 comments
Closed

Using union types together with literal singleton types #1495

joost-de-vries opened this issue Sep 5, 2016 · 7 comments

Comments

@joost-de-vries
Copy link

This 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! 😃

@smarter
Copy link
Member

smarter commented Sep 5, 2016

  1. Union types with singleton types is something I'll try to look at in the next few weeks.
  2. Currently 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/Scala2
  3. Your example works if you replace val pet = getSmallPet() with val pet: Fish | Bird = getSmallPet() or alternatively if you put import scala.language.keepUnions at the top of your file (note: I'm pretty sure this should be import dotty.language.keepUnions, we seem to have a bug here). Currently we never infer union types without the keepUnions 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.

@smarter
Copy link
Member

smarter commented Sep 6, 2016

@felixmulder Your example works fine on the latest master, probably fixed by #1491

@felixmulder
Copy link
Contributor

The example in question was:

object Test {
  object Monday
  object Tuesday
  object Wednesday
  object Thursday
  object Friday

  object Saturday
  object Sunday

  type Weekday = Monday.type | Tuesday.type | Wednesday.type | Thursday.type | Friday.type
  type Weekend = Saturday.type | Sunday.type
  type AnyDay  = Weekday | Weekend

  def main(args: Array[String]): Unit = {
    println("Monday is weekday: " + Monday.isInstanceOf[Weekday])
    println("Saturday is weekend: " + Saturday.isInstanceOf[Weekend])
    println("Sunday is weekday: " + Sunday.isInstanceOf[Weekday])

    (Monday: AnyDay) match {
      case _: Weekend => println("shouldn't match")
    }
  }
}

Which before #1491 would yield:

Monday is weekday: true
Saturday is weekend: true
Sunday is weekday: true
shouldn't match

@joost-de-vries
Copy link
Author

See #1551

@joost-de-vries
Copy link
Author

This comment by Felix Smarter describes the performance concerns in more detail.

@joost-de-vries
Copy link
Author

Closing this since it is unlikely to be changed, witness #1532

@joost-de-vries
Copy link
Author

Fixed by #6299 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants