I agree with 100% Java interop as well as union type is implicit. but nevertheless Kotlin.js need Union Type for JS interop. Yes, parameter of union type can be replace with method overloading but javascirpt can be return union type as follow.
this IDL can be translate to kotlin code as follow.
public external abstract class HTMLAllCollection {
...
fun item(nameOrIndex: String = definedExternally): UnionElementOrHTMLCollection?
}
public external @marker interface UnionElementOrHTMLCollection {
}
this is not easy to understand and need to type casting. but what if kotlin.js have union type can be simplify as follow. furthermore dynamic keyword can be replace by union type for explicit.
public external abstract class HTMLAllCollection {
...
fun item(nameOrIndex: String = definedExternally): (Element | HTMLCollection)?
}
The Java interop argument regarding union types doesn’t seem consistent with coroutine design choices. You pretty much need a third party library for Kotlin coroutine features to be usable from Java code. Seems to me that there are many potential union type designs that wouldn’t be nearly as messy to call from Java as suspending code is with no additional libraries. In my opinion, it’s ok to require a third party library on the Java end to make calling some Kotlin features clean as long the Kotlin team provides this support.
Although some of the other arguments about maintainability and readability do seem valid. I don’t think simply allowing | operator in between types anywhere in the codebase is the right choice as I think it will be messy and overused.
Maybe I misunderstood something, but to me it seems that the thing you actually want is ducktyping or being able to implement interfaces for 3rd party classes. Personally, I really like how Rust solved this issue…
Sorry for the late reply but I don’t get any notifications from discourse when someone writes something.
but to me it seems that the thing you actually want is ducktyping
Union typing is dynamic typing but its runtime polymorphism is bounded.
Rust can do this with existentials which may be possible in kotlin if kotlin adds typeclasses and typeclasses are first class.
But there are valid points for union type because operations which exists on all its element types are implicitly available for union types such that for a l1,l2:List<Union<Int,Float,Rational>> you can implicitly map(+,l1,l2) without to create a typeclass/trait for this.
Further, union types are structural and they subtype, does trait object sprovide the same?
I would actually go a step further, and instead of Pair<String, Any?> or Pair<String,JsonValue> also define JsonPair class with the convenient constructors for it, too.
The point is the a JsonValue Classis unbounded, even a Rust trait a.k.a as tyepclass is also unbounded.
Yes, parameter of union type can be replace with method overloading but javascirpt can be return union type as follow
Then you could also state that subclassing replaces method overloading, but there is a difference here, method overloading is a static variant of ad hoc polymorphism whereas union types and subclassing are runtime variants of it, they should be orthogonal.
You could also argue that typeclasses replace method overloading, but we need typeclasses anyway.
Generate wrapper class with constructors for all possible values?
Then you implement union types over and over again from. Furthermore, you will need compiler support such that methods accepting all types accepted by your Json type accepting also the Json type itself and implicit extract the json inner value into a value of its accepting types.
The last of these overloads is for “throw your hands in the air and hope you have one of those types”. We could do better if we had polymorphic dispatch, which is related to (and can be solved by?) union types.
You don’t need polymorphic dispatch for this scenario, rather it would be better to provide only one signature of type union.
I was thinking this could be why; if you have a union type that would generate overloads, you might run into a conflict with a separate, manual overload.
Good point, what we need are either overload conventions for ambiguities or we don’t compile.
Ducktyping is different. Expressed with the words of ducktyping: Union types allow you to define a Duck and you are still allowed to implement a Frog which quacks like a Duck, but isn’t a Duck.
Based on this you can define:
a function which take Ducks as parameter
and a function which take Frogs
and another function which takes Ducks or Frogs : This would accept the union type (Duck|Frog) and then distinguish what to do with Frogs and what to do with Ducks - or do something which it can do with Ducks and Frogs equally. But the union-type ensures, that it will never be called with a DuckOrFrogSimilar thing which is neither a Duck or a Frog.
not sure if it helps, but ceylon (https://ceylon-lang.org/) have union type support… and union type generally ends up with a split of code (so a when in kotlin).
that been said, union types is a good thing, I believe.
enum class Device {
SCREEN,
KEYBOARD,
MOUSE,
CPU,
RAM,
DISK;
}
enum class InputDevice {
KEYBOARD,
MOUSE;
}
val InputDevice.asInputDevice
get() = this
val Device.asInputDevice: InputDevice
get() = when (this) {
Device.KEYBOARD,
Device.MOUSE -> InputDevice.valueOf(this.toString())
else -> throw Exception("Device is not of “InputDevice” kind")
}
fun operate(device: InputDevice): Unit {
print("Operating: ${device}")
}
fun operate(device: Device): Unit {
print("Operating: ${device.asInputDevice}")
}
fun usage(): Unit {
operate(Device.KEYBOARD.asInputDevice)
operate(Device.CPU.asInputDevice) // Not gonna work
}
It is an example where developer has no control on classes implementation and pass Any then use when to switch on type… But he need a else then… #FeelsBadMan.
Union type would (actually could, depend on use of the function…) fix the else problem.
That’s been said the when still feels bad: the repeat again and again… the perfect solution would be to find a way to implement a “trait” (ideally an interface, but I don’t think it would technically be possible on jvm). Something like in rust… (I admit this is a part I love about rust). This would make the union type obsolete and would be very more powerful.
Chalk up another vote for union types here. My simple problem is wanting to define a possibly nested dictionary. Sure there are workarounds that either admit more types (which need to be detected at runtime – in which case I might as well use js or python):
var map = Map<String, Any>
Or waste conceptual and actual space:
class Thing {
var which:Boolean
var s:String
var m:Map<String, Thing>}
var map = Map<String, Thing>
But what I really want to do is (note this is especially interesting because the data type is recursive, so imagine that “thistype” is a primitive referring to the currently-being-defined type):
var map = Map<String, String | thistype>
The above data structure definition would work with Kotlin’s “when” expression to make a very expressive and succinct combination.
As mentioned above, this is not possible with primitive types (perhaps possible with inline classes? Or not?). Maybe most developers except those working on I/O libraries need to use that, but it is still a need.
Typesafe union types in general aren’t really possible with primitive types. You need some general info which type is actually present at runtime and not all primitive types take the same space. Apart from that, Maps in general don’t work with primitives.
The RecursiveMap version is the right one if you require clean and robust code. The alternative would be to simply use Map<R, Any> and use either other Maps or values as map-values and check the type at runtime. You can of course encapsulate that so that the raw data structures aren’t visible from the outside.
@Wasabi375 Never delegate to maps like that. Your RecursiveMap class will not implement equals and hashCode properly and your map will be incompatible with other maps. Either also manually delegate both methods, or just have the map be a public val instead of a delegate.
Never mind, I was daydreaming when I wrote that. I was referring to the use of union types to generate overloads for primitive type API methods in a library for data storage.
yes, that works, and I appreciate it since I am new to Kotlin and it wasn’t a solution that I came up with. But I feel that my comment still stands – or actually is even stronger. Its about being able to express the concept succinctly. I feel that Kotlin is trying to break from languages like c++ and java in attempting to reduce boilerplate code and eliminate complex multi-line constructs to implement simple concepts. Or anyway, the existence of features like “by”, string templates, inline if, function dev = value, and “when” make me think so.
Why does your argument make the idea of having Type | Type stronger? Because it is a very succinct and unambiguous way to describe what Kotlin clearly already values and implemented in sealed classes. If this notation was syntatic sugar for the construct you’ve described then the changes needed are only in the parser, not the language, yet it would make the code shorter and easier to understand.