Hi, I designed those. :)
They were inspired somewhat by Icon's notion of goal-directed execution. You can also think of them as another special kind of spread in that a spread produces zero or more values which get interpolated into the surrounding context.
The if
syntax looks somewhat like languages where if
is an expression, but that's actually <em>not</em> how they work here.
Loop comprehensions (especially as in Python) and Ceylon's comprehensions in argument lists also gave me good food for thought, especially the latter.
I don't know of any language that has these exact features, but I tried to pull from all of the above. I also spent a lot of time looking at existing Flutter and other Dart code to get a feel for where the pain points are and what changes would help.
To expand the list, I believe Ceylon and Typed Racket have it.
For the record, Ceylon IDE does that too.
We used it extensively to port a bunch of legacy code in our IDE codebase from Java to Ceylon. It's not perfect, since there are things it can't know (the big one is "can a Java variable be null?"), but it sure saves a lot of time.
~~Referenced link defines an ADT, not a union type~~
On the JVM see Ceylon for proper union types
EDIT
The only difference seems to be in the definition? i.e. both sealed classes in Kotlin and union types in Ceylon are equivalent, though in the former the burden is on the user to define a sealed hierarchy, while in the latter the compiler effectively translates A | B | C
to a sealed hierarchy.
Now I'm curious, is there a semantic difference between these 2 definitions?
type ABC = A | B | C
sealed class ABC { object A : Expr() object B : Expr() object C : Expr() }
It would seem they are equivalent, though I wonder if one could, say, define a method that takes a subset of A | B | C
like A | B
. If so then sum types and union types are distinct concepts and not just syntactically different.
Ceylon and Crystal both have union types. You can think of union types as representing a tagged*, disjoint union of types, so int | float
represents either an integer or a float. A Rust style enum,
enum Enum { Case1, Case2(i64, i64), Case3 { x: i64, y: i64 }, }
can be thought of as the composition of the type declarations
struct Case1; struct Case2(i64, i64); struct Case3 { x: i64, y: i64 };
and the union type
type Enum = Case1 | Case2 | Case3;
The power of a union type comes about through a mix of things: they are composable (you can pass a Case2
to a function, or have a hierarchy of more specific enums), they are truly algebraic (eg. The signature of reduce() in Ceylon), and in some more complex sense they "complete" the types (<code>mlsub</code> made the rounds because it "solves" a challenging problem, which was only made possible with the help of union types).
Given union types are strictly more expressive than sum types, there's little reason not to prefer them. The one issue I can think of is that deciding on the tag can be less trivial, but that's far from a blocker.
As to what makes std::variant
a mixed breed, it discriminates internally like a sum type, in that std::variant<int, int>
≢ std::variant<int>
, but you construct and match on it as if it was a union type, so a generic std::variant<T, U>
is liable to break should T
and U
be equivalent. Neither union nor sum types share this issue.
* Ceylon is Java-based, so its unions use the dynamic type tag, whereas Crystal has value types and a union needs an explicit tag.
Hmm. I wonder about existing Java enums. They're barely mentioned and yet enums are a typical jumping off point for sum types in some other languages?
And there's the interesting little fact java technically already has union|types, though currently limited in the language to catch blocks. How about generalising that out, ceylon-style? Ceylon doesn't seem to get much notice, but is kind of neat too.
Actually there is a Ceylon plugin for IntelliJ!. I think they switched focus from Eclipse to IntelliJ after Google switched for Android development. It's pretty good, but probably not as good as Kotlin's support for obvious reasons.
I agree that Ceylon isn't as popular as Kotlin but I haven't given up hope yet! It's a really nice language. Even if it never takes off, I'm really happy to see that its ideas are spreading to other languages like TypeScript and Kotlin.
Anyway, I didn't mean to offend. I just thought it sounded like a good alternative given your constraints. I hope you enjoy using Kotlin!
In Ceylon, null
is just the single instance of the type Null
(any type can enumerate its instances), so when you have something of type T
that may be nullable, you say its type is T | Null
(which can be shortened to T?
but that's just sugar), which uses Ceylon's other feature, union types (other example could be Success<T> | Failure
which is really useful). So there's no special case in Ceylon just for null (though syntax sugar for working with null was added to to make it easier to handle, such as if (exists item) { /* item is not null here*/}
, and return item else defaultValue
, which returns a default value if item is null).
The comment about tuples and functions refers to the interesting fact that function arguments in Ceylon are Tuples (all functions have the generic type Callable<Result, Arguments> given Arguments satisfies Anything[]
, where Anything[]
means any tuple), which allows you to handle function references and invocation in rather advanced ways... see the Tour of Ceylon chapter on functions.
Not true actually. Ceylon is a JVM language with Union Types..
https://ceylon-lang.org/documentation/1.3/tour/types/
I've not used it but the concept I think would be extremely useful. As an example, I was working with a chart library where data could be either Int or Double. Was trying to write a wrapper so that the dev didn't need to know the sequence of calls needed to make the chart, they just implemented a function to return the data.
Now clearly I could have an interface IntegerChart and a DoubleChart but it would have been nice to say 'you have to write a function which returns either a list of Int or a list of Double.
Right now, I could do AbstractChart<T> but can't enforce the type of T easily whereas a union type would allow it
FWIW, Ceylon calls these union types, and does matching using its normal instanceof operator and flow-sensitive typing:
void printType(String|Integer|Float val) {
switch (val)
case (is String) { print("String: val
"); }
case (is Integer) { print("Integer: val
"); }
case (is Float) { print("Float: val
"); }
}
I don't think that helps Rust much.
>Perhaps adding an option to the reified annotation to specify the type of wrapper that you want to be used,
I'm not sure it is possible to know at compile time what representation would be required. Maybe an array of classes? a Map<String, List<Integer>>
would have an additional [String.class, List.class, Integer.class] array, looks like the order in the signature would be enough to assign a specific class to a given type parameter.
Maybe you already know, but back in the day where was another JVM base language, Ceylon, that had full reified parameters (event better than Kotlin). I think it never had any popularity, but maybe it can help you to steal some ideas.
Oh hey. Someone else wrote about this too lol. I thought about this too and wrote about it. “Kotlin destructuring considered harmful” by David Stocking https://link.medium.com/WuKYCYIc5R . I like checking out every languages under the sun and a blog on Ceylon discussed the same thing that Kotlin did. https://ceylon-lang.org/blog/2012/09/13/destructuring/ It is one of the reasons I hate when Kotlin says it's "pragmatic". Because it's more of a "we think it's good" and in this case it's more harmful then useful when used this way. Anyway nice article, maybe we can get it changed one of these days 😀.
> Ceylon doesn't have anything like Java's primitive types. The types that represent numeric values are just ordinary classes. Ceylon has fewer built-in numeric types than other C-like languages: > > * Integer represents signed integers, and > * Float represents floating point approximations of real numbers. > > However, the compiler magically eliminates these classes, wherever possible, in order to take advantage of the high performance of the platform's native primitive types.
https://ceylon-lang.org/documentation/1.3/tour/language-module/#numeric_types
yep, compiles to javascript, which technically runs in a javascript vm in the browser, though i agree that's a potentially confusing way to put it.
the lack of good examples and documentation of javascript interop does seem to be a weak point, but if you go to http://try.ceylon-lang.org/ and click on the "dynamic interfaces" example you can see some code, also https://ceylon-lang.org/documentation/reference/structure/dynamic/ shows you how to use a window
object from javascript as a typed object in ceylon by defining an interface for it.
i played with ceylon for a bit but decided i liked elm better; the language still looks excellent, though.
I don't know how this thing handle them, but it is somehow possible: The people of Ceylon language did it and you can have full reified generics in the JVM. However, Ceylon was never a hit and it is in Eclipse's hands right now.
??
and conditional access ?.
- languages seem to think once they handle optional types correctly they don't need the syntactic sugar, but it is still really niceCounterpoint: Ceylon, a language that targeted the JVM and the JavaScript VM, had reified generics, a typesafe metamodel, and declaration-site variance. It also supported use-site variance for Java interoperability. It also had a bunch of other goodies like union and intersection types, flow-based typing, and comprehensions.
Of course, development seems to have come to a halt shortly after RedHat transferred the language to Eclipse, so maybe it doesn’t count. I’m not claiming that it was “more powerful” than any other language either. I just like to bring it up at every opportunity because it was a pretty nice language.
Java has union types in multi-catch blocks, but that’s not super useful. It also has intersection types, but only in generic constraints.
Ceylon had union and intersection types, targeting the JVM and JavaScript, and maybe partial support on the Dart VM for whatever reason. Of course, it never took off so it doesn’t matter.
>I don't agree with pretty much any of the suggestions.
I'd be interested to hear some arguments against the short constructor inheritance syntax, which seems like the least controversial one to me.
>But the author may consider using Ceylon given his concerns.
It does have some really nice features, but the lack of popularity (and the resulting much smaller ecosystem of tools and libraries) is a problem.