Skip to content

Instantly share code, notes, and snippets.

@KyLeggiero
Last active May 13, 2018 19:24
Show Gist options
  • Save KyLeggiero/1582a959592cadcfee2a0beba3820084 to your computer and use it in GitHub Desktop.
Save KyLeggiero/1582a959592cadcfee2a0beba3820084 to your computer and use it in GitHub Desktop.
Proposal: Kotlin collection literals
@alanfo
Copy link

alanfo commented Apr 11, 2018

@BenLeggiero

I accept, of course, that using arrays in the fashion I've described is more complicated than using List<T> and more difficult therefore for people to get their heads around, particularly when it seems to be at odds with the standard library's philosophy of preferring lists to arrays.

However, my fear is that sequence literals won't be useful as they might otherwise be. For example, faced with a choice between:

val a2: IntArray = [1, 2, 3]
val e2 = intArrayOf(1, 2, 3)

I think a lot of folks would just stick with the latter.

Also regardless of whether lists or arrays are the default, there is the fundamental difficulty that given a choice between:

val a1: Array<String> = ["a", "b", "c"]
val e1 = arrayOf("a", "b", "c")

nearly everyone would choose the latter because the factory functions can infer their type and it's therefore shorter to write.

However, where I think your proposal scores heavily is that it makes multi-dimensional tables much less verbose than they currently are. It's clearly better to write:

val t2: Array<IntArray> = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] 

than:

val m2 = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5, 6), intArrayOf(7, 8, 9))

In fact, this the main reason why I'm in favor of sequence literals at all as I don't think the present system of using factory functions for single dimensional sequences is all that bad.

So, in conclusion, I hope you'll put it forward as a KEEP and (FWIW) I'll still support it as it stands even if I'd have preferred arrays to be the default :)

@KyLeggiero
Copy link
Author

@alanfo I think you're focusing too much on my example usage which was chosen to make the type inference clear. You forget other times when the amount of code is reduced dramatically because the type is not part of the line:

foo.intArrayTypedField = [1, 2, 3]
foo.intArrayTypedField = intArrayOf(1, 2, 3)


functionThatTakesAnIntArray([1, 2, 3])
functionThatTakesAnIntArray(intArrayOf(1, 2, 3))


functionThatReturnsAnIntArray_1() : IntArray {
    // do stuff?
    return [1, 2, 3]
}
functionThatReturnsAnIntArray_2() : IntArray {
    // do stuff?
    return intArrayOf(1, 2, 3)
}

So the pros for each approach are, as far as I am aware:

List Array
(And specialized array types)
Immutability Generally more efficient
Custom implementations
Ubiquitous in Kotlin stdlib
Easier for beginners

So, since it seems the amount of code is only ever equal to or less than the current amount, combined with all the reasons about wanting to prioritize decreasing confusion and over catering to niche uses, I won't be adopting your approach into this proposal.

In addition, I would hope that, alongside adopting this into the language, stdlib would deprecate (and eventually drop) the current top-level-factory-function-based approach to sequence generation.

On a side note, I also hope we someday get even better inference, so we don't have to specify the generic type, like:

val x: Array = ["a", "b"] // Inferred Array<String>

I know that's not possible now, but it might be someday and that would make my life much better :)

🍻 Thanks for your support and great arguments!

@reitzig
Copy link

reitzig commented Apr 16, 2018

FWIW, I agree that introducing : as new syntax is not necessary, and I'll add that it's not in line with how Kotlin reads today. Due its use in typing, : reads as "is a". Here, we want "maps to" or "if lhs then rhs" which is most closely matched by ->. But I honestly think that using pairs via to for dictionary literals is fine.

@KyLeggiero
Copy link
Author

@reitzig Yeah, I agree. Maybe = would be a better fit, like we use in named function parameters. I'm still ruminating, but I'm close to finalizing. Will edit more this week before making a KEEP.

@alanfo
Copy link

alanfo commented Apr 18, 2018

@BenLeggiero

I've found the link to the Future Features Survey now and, if you check out feature no. 6, you'll see that JB themselves suggested '=' as the separator for map literals.

Another possibility for you to ruminate on would be to use a prefix to distinguish map literals from sequence literals. If you did this, then you could still use 'to' as the separator for the former.

The hash symbol '#' suggests itself as a suitable prefix (and mnemonic) for maps and I don't think it would clash with anything else. So, for example, you'd have:

val list1 = ['a' to 1, 'b' to 2,  'c' to ])  // List<Pair<Char, Int>>

val map1 = #['a' to 1, 'b' to 2,  'c' to 3]  // Map<Char, Int>

@ssadedin
Copy link

Some random thoughts from someone whose opinion should probably not be given a whole lot of weight since I'm relatively new to Kotlin: as a long time Groovy, Python and Javasript user I very much appreciate the concision of Map literals in those languages. It is incredibly useful in creating DSLs, using in REPL contexts, and many other situations. I feel like when there's a common shared syntax between many languages with high similarity, there's a strong benefit in just using that too unless it sharply deviates from your principles. And one of the things I'm liking about Kotlin is that it doesn't seem to gratuitously differentiate its syntax from that of other languages : where it make sense for things to be the same they mostly are.

Which is all to say, I'd prefer a single character, either : or = as the map character, even if it does mean new syntax / slightly deviating from other conventions used in Kotlin.

@KyLeggiero
Copy link
Author

@alanfo thank you for that! I don't think I like the = operator being used here because it would be unclear that assignment is not happening. For example:

var foo = "Foo"

var bar = [foo = "Bar"]

This seems, to me, unclear whether you get a map of foo to "Bar", or a list of foo, where foo has the value "Bar".

Using the hash symbol in this way certainly does not clash with current features, but... I would not be confident attempting to claim it for this feature, as it seems more fit for conditional compilation if we ever have that. Good idea, though, to use a special symbol as part of the operator.

@KyLeggiero
Copy link
Author

@ssadedin I agree with you. Kotlin, among many other things, aims to be easy to learn for those who already know another programming language. It would be silly for us to use a brand new syntax for something so commonplace. That's why I chose the same syntax used in Groovy and Swift, which only barely differs from that used in Python and JS (square vs curly braces).

@alanfo
Copy link

alanfo commented May 8, 2018

@BenLeggiero I agree with you that ':' seems on the face of it better than '='.

However, there was a development recently in another long running debate - whether to include the conditional (ternary) operator in Kotlin.

I've always been against this, partly because it's unnecessary when we already have the if/else expression, but mainly because '?' signifies something to do with nullability in Kotlin.

I thought that the latter would be the critical point but it turns out that the ':' is the problem! This is because the Kotlin team want to reserve its use for something else such as slices. So, if you do propose it for 'map' literals, you might run up against the same objection.

@KyLeggiero
Copy link
Author

Thank you for the insight, @alanfo! That's very useful :)

@KyLeggiero
Copy link
Author

KyLeggiero commented May 11, 2018

I've released the first draft of the collection literals KEEP proposal. You can view it on my GitHub here:
https://github.com/BenLeggiero/KEEP/blob/collection-literals/proposals/collection-literals.md

I have started going over it and refining it to become a final draft before submitting a pull request. Any comments about it here would be greatly appreciated!

@KyLeggiero
Copy link
Author

KyLeggiero commented May 13, 2018

This is now in a KEEP proposal: Kotlin/KEEP#112
Please place further discussion there :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment