5 ms·
I was looking at dotty/scala 3 earlier in the year and I was quite surprised to see familiar friends from Typescript in the form of literal types and union type
by a_humean 5y ago
I was looking at dotty/scala 3 earlier in the year and I was quite surprised to see familiar friends from Typescript in the form of literal types and union types.
val scala: "Scala" = "Scala"
const typescript: "Typescript" = "Typescript"
"Scala" | "Typescript"
- KMag 5y agoThis creates two types with quotes in their type names, and then creates a union type of the two? Then the compiler needs to determine if the token's context requires a type or a value, and disambiguate accordingly? In the example above, the created type "Scala" is distinct from a type Scala, correct? I did some quick poking around for Scala literal types, but I found stuff like https://github.com/jeremyrsmith/literal-types https://github.com/jeremyrsmith/literal-types , which don't look like the example above.
- nicktelford 5y agoNo. It's a Singleton type, which means that instead of just being a String, it has to be exactly the String "Scala". i.e. val x: "scala" = "scala" // would compile val y: "scala" = "java" // would produce a compile error Union types are an orthogonal concept, and work as you'd expect: val z: String | Int = "foo"
- KMag 5y agoInteresting, and I presume it works for singletons of any type, correct? val x: 1 = 1 val y: 2.718281828 = 2.718281828 That's kind of cute. I presume its main purpose is for overrides in order to get something similar to template specialization. C++ templates can be specialized by types and by values, but Scala method overrides are only by type, and literal types allow you to present a value as a type, so if the value can be statically proven to match, then the override is called?
- paavohtl 5y agoIn TypeScript perhaps the most important use case for literal types is the ability to to create discriminated unions, or algebraic datatypes (ADTs). Many modern languages have a separate feature for this (such as enums in Rust), but in TypeScript they can be constructed using literal and union types. Here's an example: interface ProductOffer { kind: 'productOffer' eans: Ean[] discountPercentage: number } interface GroupOffer { kind: 'groupOffer' groups: GroupId[] discountPercentage: number } type Offer = ProductOffer | GroupOffer Also check the TypeScript handbook for more information: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions https://www.typescriptlang.org/docs/handbook/2/narrowing.htm...
- valenterry 5y agoI think it is important to emphasize that there is actually an important difference. Union types don't allow to model GADTs, so they are less powerful than what some other languages offer. But on the other side, they allow for much easier composition. E.g.: type Citrus = "Orange" | "Lemon" type Prunus = "Plum" | "Apricot" Now in typescript you can just do: type Fruit = Citrus | Prunus But in many other languages it's not so easy and you have to jump through hoops and e.g. either redefine the types and/or refactor the code (if you can even do that - if the code is from 3rd party libraries you are out of luck).
- lmm 5y agoIt's not really about specialization (types in Scala tend to be parametric most of the time) so much as just being able to have values in types, so that you can have types like "ListOfLength[1]" or "MapContainingKeys["Foo" :: "Bar" :: Nil]" without having to do some horrendous encoding of those things as types.
- paavohtl 5y agoLiteral types quite simply means that values are types. For example in TypeScript the literal type "Foo" is a special string type, where the only valid value is the literal "Foo" – assigning "Bar" to a variable with the type "Foo" is a compile time type error. Literal types can be combined into union types like any other type.
- cutler 5y agoSounds like more Scala sophistry. I thought they were supposed to be aiming for pragmatism with Scala 3.
- esarbe 5y agoI can think of plenty cases where I would want to limit the input space of a function to a known set of literals. Just as an example; the `document.getContext` [0] api takes a string argument to select the context canvas. Without literal types, you cannot enforce this to a set of valid values. Literal types gives you the power to tell the compiler, which values are valid. Since there are other programming languages that support literal types, it's not just Scala sophistry. :-P [0] https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasE...
- nsonha 5y agothat's exactly how it works in TS. You can call TS sophistry then. Most people I talk to put it on the pragmatic side
- doikor 5y agoThis is just making clear and easy to use something that people did using some type level magic (see shapeless Witness). Something that people used through ugly hacks becoming part of the language natively in a easy to use and understand (and faster to compile) way sounds very much like pragmatism to me. Here is the source code to Witness (and other singleton stuff in shapeless) https://github.com/milessabin/shapeless/blob/main/core/src/main/scala/shapeless/singletons.scala https://github.com/milessabin/shapeless/blob/main/core/src/m...
- dtech 5y agoI played with it a bit because I really like TS's union, literal types and smart-casting, unfortunately it doesn't seem quite there yet and I found it only usable for T | null types. Syntax is all there so maybe it'll get better in 3.1+
- valenterry 5y agoWould be interesting if you can provide some examples. I'm happy to check them and turn them into tickets / feature requests for you.
- dtech 5y agoI encountered them quite soon, e.g. something like this [1]: val result: Int | "INTERNAL_SERVER_ERROR" = 1 val m: Int = result match { case "INTERNAL_SERVER_ERROR" => throw new Exception("Uh oh") case t => t } It does work with a ClassTag and t: T, but then it thinks the match is not exhaustive. Compare that to Typescript [2]. In this specific language feature TS is kinda the gold standard. For example they can automatically narrow the type based on fields existing or not and their type, which can be used to provide a pretty decent implementation of sum types [3] [1] https://scastie.scala-lang.org/oMdkLBIiRsqUkXv6qbP61g https://scastie.scala-lang.org/oMdkLBIiRsqUkXv6qbP61g [2] https://www.typescriptlang.org/play?#code/MYewdgzgLgBATgUwgVwDZQFwzMgtgIwThgB8YAiASQDkAVAUQCVqBBAGQH0BlJgNSY5NGAeUbkYAXhgBGAFCyAZsjDAoAS3AwADnBDAkEADy0AfAApEKdFlqkKNBs3bc+AoaPIBKGzADesmECYNQULJDRYCSiYAHIHJlZOHkZ+RkFGEUYYzz8AoPyoAAtdAHdsBDL6OF04MxiAVUKYEELsvMCAX3b4BChkODAeqygAblku2VBIWABPLBwCIkltXX0ICDDhzyA https://www.typescriptlang.org/play?#code/MYewdgzgLgBATgUwgV... [3] https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions https://www.typescriptlang.org/docs/handbook/unions-and-inte...
- valenterry 5y agoLiteral types ("Scala" instead of String) existed before typescript to my knowledge. But union types are such a great thing. I'm very happy to see Scala catching up with typescript, as it has proven to work out really well.