tl;dr - Sequence literals allow APIs to avoid leaky abstractions.
I was merely giving an example of expressive syntax that can be achieved through well-designed features of a language and its standard library. Such good design would not necessitate something so specialized as “an OptionSet literal”, as it would be generally-useful enough to enable things like OptionSet
and other out-of-the-box usages of syntax without any proposals.
I was not intending to introduce discussion around implicit member expressions, nor RawRepresentable, nor SetAlgebra.
The entire point of my example was to show how the low-level and often ugly task of creating and using a bitmask can be semantically described in an elegant and human-friendly way, thanks to features of the language and standard library harmoniously working together. It was not to say “here’s something that is impossible in Kotlin,” but instead “here’s something that is so clunky in Kotlin that devs avoid doing it entirely.”
I also chose OptionSet
specifically because it’s most-often used to hard-code configure something, such as a UI control or network request, rather than arrays and dictionaries which can commonly be read from a file.
The bracket notation for collections doesn’t give you much over vararg–in fact, I bet someone would propose dropping the brackets.
Odd you bring this up - my original proposal had the bracket notation be syntactic sugar for a operator fun
with vararg
.
Additionally, the brackets are there to say “here is one singular object,” not “here are three separate objects.” It extends to all places where an OptionSet
might go:
struct Example {
var options: ShippingOptions = [.priority, .standard]
func evaluate(against options: ShippingOptions = [.nextDay, .secondDay]) -> Bool {
self.options.contains(options)
}
mutating func reset() {
options = [.priority, .standard]
}
static func suggestedDefault() -> ShippingOptions {
return [.priority, .standard]
}
}
Let’s look at this example without any syntactic sugar, using only what we can do in Kotlin today, assuming all necessary functions are well-implemented somewhere else:
data class Example(var options: ShippingOptions = ShippingOptions(ShippingOptions.priority, ShippingOptions.standard)) {
fun evaluateAgainst(options: ShippingOptions = ShippingOptions(ShippingOptions.nextDay, ShippingOptions.secondDay))
= 0 != this.options.rawValue and options.rawValue
fun reset() {
options = ShippingOptions(ShippingOptions.priority, ShippingOptions.standard)
}
companion object {
fun suggestedDefault() = ShippingOptions(ShippingOptions.priority, ShippingOptions.standard)
}
}
And once again pretending all these discussed language and stdlib features are implemented except collection literals:
data class Example(var options: ShippingOptions = ShippingOptions(.priority, .standard)) {
fun evaluateAgainst(options: ShippingOptions = ShippingOptions(.nextDay, .secondDay))
= this.options.contains(options)
fun reset() {
options = ShippingOptions(.priority, .standard)
}
companion object {
fun suggestedDefault() = ShippingOptions(.priority, .standard)
}
}
And finally, pretending only collection literals were adopted:
data class Example(var options: ShippingOptions = [ShippingOptions.priority, ShippingOptions.standard]) {
fun evaluateAgainst(options: ShippingOptions = [ShippingOptions.nextDay, ShippingOptions.secondDay])
= 0 != this.options.rawValue and options.rawValue
fun reset() {
options = [ShippingOptions.priority, ShippingOptions.standard]
}
companion object {
fun suggestedDefault() = [ShippingOptions.priority, ShippingOptions.standard]
}
}
As you can see, whether or not Kotlin has IMEs, SetAlgebra, RawRepresentable, etc., it still becomes more readable and more elegant with collection literals. It communicates that it deals with a collection of some sort, rather than performing some operation on the given shipping options (regardless of how things work under the good). The user doesn’t have to know what a ShippingOptions
is under the hood.
Your counter-example of creating an optionSetOf(...)
function goes back to the original gripe that arrayOf
is unnatural, and that API users must memorize far too many someCollectionOf
functions just to do what other languages let you do with two characters: [
and ]
. That’s why my original proposal included getting rid of these entirely.
This also means that, if something becomes implemented differently under the hood, the syntax is not portable: If an SDK goes from taking a List
to taking a Set
, the APIs must then either break by requiring setOf
, or duplicate new APIs must be made to marshal lists to sets, which bloats the API space and runtime. That must be done for these APIs since general-purpose sequence literals can’t be passed - at best it must be a listOf
or setOf
or mutableListOf
or mutableSetOf
or or or or or.
Thanks to first-class support for collection literals, Swift programmers generally do not need to know (and I bet many beginners do not know at all) that the Array
and Dictionary
types are actually called that. They don’t need to, since the language syntax covers that. They also don’t need to know what an OptionSet
is unless they’re making their own, nor how it uses array literal syntax; all they need to know is that they can use this familiar tool in this familiar place.