-
Notifications
You must be signed in to change notification settings - Fork 361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Collection literals #112
Collection literals #112
Conversation
365aa38
to
c76167d
Compare
I couldn't understand from the proposal, what declarations exactly are you suggesting to add to the standard library, so that collection literals would be available for all standard Kotlin collection types? For example, to make this code possible:
We should add at least the following declarations:
But these are conflicting overloads and Kotlin does not allow that. |
@udalov I believe this proposal implies allowing these conflicting overloads for |
Great observation, @udalov. I only found that issue late into the final draft. |
Having the same syntax for lists and sets contradicts the point of beeing concise, since you have to specifiy the type explicitly: val myList = [1, true, "three"]
val myEmptySet: Set<String> = [] And it would be inconsistent. Why is Combining a collection literal with a type conversion makes the whole approach absurd: val b2 = [1, 2, 3] as MutableList<Int> The existing approach is much cleaner and consistent: val b2 = mutableListOf(1, 2, 3) If the goal is to make the code more concise, one could use mnemonics like "s" for "set" or "m" for "map": val myMap = m("a": 1, "b": 2)
val myList = l(1, 2, 3)
val mySet = s("a", "b", "c") However this doesn't play nicely with mutable collections -- but this is true for the syntax proposed in the KEEP as well. I'd go as far as to say that the literal syntax should only exist for immutable collections. But if we leave it as is we have a consistent way for mutable and immutable collections. Personally I like the way of Scala best: val myMap = Set("a" -> 1, "b" -> 2)
val myList = List(1, 2, 4)
val mySet = Set("a", "b", "c") It looks much better than the All in all I'd prefer to leave the syntax for collections as is, if the alternative (as proposed in the KEEP) would introduce so much inconsistencies. |
Collection literals may be more relevant for passing as parameters. For example, if you have |
@accursoft If collections literals are used for passing them as parameters, one could pass the collection elements as To translate your example in this style: fun foo(vararg list: Int) = ...
foo(1, 2, 3) The builder style would look like this: SomethingComplex.a(1, 2, 3).b(2, 4, 6, 8).c("one" to 1, "two" to 2).build() Where
The builder in this example would look like this: class SomethingComplex(a: List<Int>, b: Set<Int>, c: Map<String, Int>) {
// do something useful with a, b, and c
class Builder(private val a: List<Int>) {
private var b = setOf<Int>()
private var c = mapOf<String, Int>()
fun b(vararg b: Int) : Builder {
this.b = b.toSet()
return this
}
fun c(vararg items: Pair<String, Int>) {
this.c = items.toMap()
}
fun build() = SomethingComplex(a, b, c)
}
companion object {
fun a(vararg a: Int) = Builder(a.toList())
}
} So it is relatively easy to make nice APIs without collection literals. |
@helmbold You right, but if you want to pass existing collection to vararg method it looks not so nice: instead of
Also vararg copies array on a method call. |
@gildor But if you want to pass existing collections, you could overload the respective functions. In my example this would be: fun b(b: Set<Int>) : Builder {
this.b = b
return this
} I don't want to deny that collections literals could be useful in some scenarios, but if I see all the compromises and inconsistencies in the proposal, I think the already possible solutions are not so bad. However, I would suggest to introduce the Scala way to Kotlin and allow something like |
@helmbold So you propose an alternative where one is required to write a builder class for each function he wants to pass collection literal to? I don't think this approach scales at all. Let's keep the discussion and critics focused on this particular proposal. If there's an alternative that goes completely in another direction, it's better to start a distinct KEEP proposal for that. |
Archived Slack discussion of first draft: https://imgur.com/a/NKw0MCF |
What this proposal currently lacks is the analysis of situations when there are multiple Examples are:
|
There is a lot of work (and complication) here to cover the non-default use cases ... are they actually common enough to be worth it? I suspect that >=90% of actual requirements would be met with just |
@accursoft Arrays is least useful case for literals imo, usually you use lists or any other collections. Arrays use case is only performance critical parts and mostly you avoid to expose arrays on you public API (they are mutable). |
@helmbold List is much more widely used collection than set or any other data structure. I think it's completely fine solution to choose most common collection as default (it's also common practice in many languages) |
@gildor I'm proposing that Not completely consistent, but highly pragmatic. |
I agree with @accursoft, data structure type is important and Moreover the "Motivation" section should be revisited
Having a cool synstax to define read-only, random-access list is pratical, same for maps. Instead having
working and
not working looks strange, IMHO. |
Let's say we could settle with parenthesis for all collection literals s"Hello, $name"
f"$name%s is $height%2.2f meters tall" ... then we would have the syntax I suggested above ( |
@gildor don't forget the motivational factor. If lists are much easier to instantiate than sets, then they will be used instead of sets even when a set is more appropriate. In my practice about 80% are lists, and 20% are sets because logic does not require ordering. If we give lists special treatment, that ratio will shift to something like 95%-5% for lists-vs-sets. We can see that effect in Java APIs, where arrays prevail over lists or sets just because they have the |
Some considerations:
has same performance of
so we consider
As alternative using "prefix dictionaries with a |
A way to fix the follow issue without any special treatment operator fun <T> sequenceLiteral(vararg elements: T): Array<T> = ...
operator fun <T> sequenceLiteral(vararg elements: T): List<T> = .. is to shift this operator into the Compantion object: operator fun <T> Array.Companion.sequenceLiteral(vararg elements: T): Array<T> = ...
operator fun <T> List.Companion.sequenceLiteral(vararg elements: T): List<T> = .. this avoid any kind of ambiguity (I hope :) Deeping into this way (merging with the Java style) it is possible rename the operator fun <T> Set.Companion.of(vararg elements: T): Set<T> = .. this allows replacing
to
|
@fvasco There is a problem with this approach: |
Hi @gildor, Is the original propostal more appropriate than this one? Do we discuss in a different KEEP a solution for the current Kotlin limitation (ie: declaring a Do we consider some alternative way to current proposal, ie: considering the syntax
as syntactically equivalent of
so it is possible to write
note: this last proposal allows the follow syntax
without adding more code |
Using type annotations like this val foo: List<Int> = [1, 2, 3] can get very cumbersome when used in nested structures (which might actually be the biggest reason to introduce this syntax sugar): val foo: List<???> = [10, 20, [1, 2, [25, 50]], ["Groovy"] as Set] |
@zxj5470 Given that collection literals are currently not supported outside of annotations, it doesn't make sense to reason what type is inferred for a collection literal when the code doesn't compile. |
Oracle had planned to introduce collection literals in Java 8, but dropped the idea. The arguments are valid for Kotlin as well. |
I have couple of performance oriented questions: it seems that in this proposal, in order to create maps the "key value sequence" will first be wrapped in a list of pairs just to be destructed back into a map. This seems to be quite wasteful. To the best of my knowledge, the JIT will not be able to do an escape analysis on such usage profile which means that the list and each pair will be actually allocated, filled, then iterated on inside the In addition, I did not find any reference to primitive collections and arrays while they are extremely important (performance wise) - do you have any plans to avoid boxing/unboxing the entries in primitive collections? (collections like Matrix, Vector, Int2DoubleMap, etc. which are especially common in scientific stuff that will probably use collection literals a lot) |
I can see how that post might imply this for you. However, it is still a JetBrains goal to have collection literals. Last time I checked with JetBrains staff about this was about 6 years after that blog post was written, and they said it was still a goal. |
Any progress on this? |
Major +1 on this - From the wikipedia page, compare the difference it made to ObjC: Example without literals: NSArray *myArray = [NSArray arrayWithObjects:object1,object2,object3,nil];
NSDictionary *myDictionary1 = [NSDictionary dictionaryWithObject:someObject forKey:@"key"];
NSDictionary *myDictionary2 = [NSDictionary dictionaryWithObjectsAndKeys:object1, key1, object2, key2, nil];
NSNumber *myNumber = [NSNumber numberWithInt:myInt];
NSNumber *mySumNumber= [NSNumber numberWithInt:(2 + 3)];
NSNumber *myBoolNumber = [NSNumber numberWithBool:YES]; Example with literals: NSArray *myArray = @[ object1, object2, object3 ];
NSDictionary *myDictionary1 = @{ @"key" : someObject };
NSDictionary *myDictionary2 = @{ key1: object1, key2: object2 };
NSNumber *myNumber = @(myInt);
NSNumber *mySumNumber = @(2+3);
NSNumber *myBoolNumber = @YES;
NSNumber *myIntegerNumber = @8; I think it would have similar improvements on readability for Kotlin. |
@Longhanks
You don't need to declare the type of the variable, it is inferred. The expression itself is more readable because it uses words and not arbitrary symbols to convey the type of the collection. |
Considering your example, I find let myArray = [object1, object2, object3]
let myMap = [key: someObject] to be much more readable, it's easier for my mind to distinguish brackets and double colons (e.g. |
If this is implemented, I think it should go like this: val list = ["a", "b", "c"] //infers List<String>
val map = {"a": "a", "b":"b", "c":"c"} //infers Map<String,String>
val set = {"a", "b", "c"} //infers Set<String> Then if you need mutable versions you just specify the type: val list: MutableList<String> = ["a", "b", "c"]
val map: MutableMap<String,String> = {"a": "a", "b":"b", "c":"c"}
val set: MutableSet<String> = {"a", "b", "c"} Or even easier val list = ["a", "b", "c"].mutable() //infers MutableList<String>
val map = {"a": "a", "b":"b", "c":"c"}.mutable() //infers MutableMap<String,String>
val set = {"a", "b", "c"}.mutable() //infers MutableSet<String> The set syntax |
@Longhanks perhaps it would help if the IDE highlighted some of the elements. Like the parentheses and the |
@Dico200 What if people use a different IDE that does not implement highlighting for such „special case functions“? I‘d rather have a special syntax that conveys such information. Other modern languages have also chosen this path, see Rust‘s Vectors or Swift‘s Arrays. Why is Kotlin opposed to the literals (except for they haven’t been implemented yet)? |
@Longhanks I do not represent the views of "Kotlin" or the Kotlin language designers. I also did not suggest highlighting to replace the proposed feature. |
Re: @gbaldeck
Thank you for this! It's always good to consider alternative approaches. However, we must keeps in mind that "because it's used in another language" isn't a reason that this should or shouldn't be done a particular way. Objectively, the reason that I landed on square brackets was (and still is) the current association of square brackets with collections, where curly braces are associated with scopes. |
I think perhaps the expectation of this Keep is unrealistic. not because anything is wrong with the proposal, just that jetbrains doesn't like @BenLeggiero who proposed it. they may freely prove me wrong here and show some common sense. there's noone who will be confounded by list operators in kotlin, so there's another reason for avoiding this one. however as demonstrated above #112 (comment) the index operator is an abusable feature so you can fake it. I am leveraging a lot of cartesian results from index operators in other matters of kotlin expressions actually so this one is very trivial compared to my personal sandbox. In my code, I seem to gain clarity and benefit from something i use in a module I call Bikeshed.kt. this hack is a nod to python's u string, which looks funny the first time you see it but does a good job. another excuse to gratuitously mention @gvanrossum as well in the same post calling out jetbrains.
|
@jnorthrup I think it's a very strange thing to say, JetBrains is not a person, it's a company with a lot of people, it cannot like or do not like someone.
A lot of things is wrong with the proposal, this is exactly why the discussion has 60+ comments And I would like to keep discussing this proposal only |
there are a few things implied that surely do get lost in text on the internet, but I've never learned to let that keep from expressing irony, and I do hope strange is what comes across. So, Kotlin has the nicest possible extension and operator overloading one might hope for, except for mind bending exceptions, like permitting unicode in backticks but outlawing operator character freedom completely. but what we've seen from Jetbrains, Kotlin team is anything but inaction, or partial resolve, or unwillingness to make bold changes to any part of the language. sometimes its in the form of a forceful judgement, but not here, this particular proposal seems willfully held back from any stewardship or guidance, that is unless you want to read an implicit preference for faux sexp. there's perhaps two likely motivations for the above:
But it's not an inconsequential feature, it also makes kotlin the outlyer among popular programming languages (except lisp) in having nothing but a varargs arity option on functions. and we know that has nothing to do with any sort of nanokernel notions or design philosophies that would typically be behind a limiting pallete. I don't see alot of compelling arguments against this KEEP, I see "kotlin experts" bikeshedding. that's my opinion. meanwhile, yes, listOf(vararg) definitely sucks beyond any of the most primadona suggestions we have seen to date in this thread. So here's mine, accompanied with working code. |
it's great that i can prompt a response from onlookers who see fit to downvote me, but I'm not actually part of the problem, I'm just complaining about it. I find kotlin to have some unintuitive features, chiefly in the decisions about operators across the board. I'm not asking for perfection, just the end of analysis paralysis. there's always going to be differing ideas of better or worse, but it seems like noone is here to kill the proposal outright, it just lacks traction with anyone who has inclination to pull it forward, and has only 33 votes on the radar when they tally their priorities. is there anyone among the conversation who has yet to upvote or downvote the PR? and umm tldr; we're not talking about a code submission are we? just getting a proposal into the proposal directory of the source tree? maybe a recap is a constructive thing. maybe a restructuring of the bullets to get votes rather than critique is more likely to benefit I think for me the lack of flowing coding conventions was annoying and disappointing enough in the process of coding that it yanked me off my immediate goal to write a somewhat more distinct hack to do command completion etc. and to share it, with a side of heartburn, which is basically what this part of the language feels like for me. so i guess it's great this is being watched, and that I'm being judged, but I'm not really the issue, collection literals is the issue for an extremely active language development getting none of the benefit of anyone in here. |
@jnorthrup |
Having slept on this for about ~477 days, my reasons for wanting something like collection literals still stand. The solution I originally proposed was a terrible one (didn't take many nights for that one). However, I do like some of the other suggestions:
It would be interesting if we could get some sort of constructor operator to do the reverse operation of a destructuring declaration. When the type is known, if the type's constructor operator could be called it would work really well with not just collections but other classes as well. data class Point(val x: Int, val y: Int)
val points = listOf(Point(1, 2), Point(2, 4), Point(3, 16), Point(4, 2))
val morePoints: List<Point> = [[1, 2], [2, 4], [3,16], [4, 2]]
fun getAPoint(): Point = [2, 4]
val mixedPoints: List<Point> = [[1, 2], getAPoint(), [y=16, x=3], Point(4, 2)] hastebrot brings up good points how there isn't much to be gained even via other features that build on top of collection literals like the spread operator. However I think they would be more powerful when instead used for class literals. So we can do something like |
Hi, @AarjavP, |
Sorry I missed it, I like it! I haven't given it much thought, but would annotating a top level function or in java a static function solve the issue? The function name does not need to be locked, what's important is the return type and the arguments for the function. Annotation and operator keyword does seem like pretty much the same thing though. Or perhaps have you thought of any other drawbacks since then? |
We must also keep in mind that just because another language does it shouldn't be dismissed. I don't think this will ever get implemented anyway. Lambda syntax would be too close to set and map syntax. Also, setOf, mapOf, and listOf work just fine. |
concrete applies to almost nothing in language design except perhaps a metric of brevity, and in the matter of but there are array literals in kotlin annotations. so the complexity of the language increases as we start to describe annotations, or declarations in the lack of annotations, whichever one makes the most sense. again. complexity isn't concrete, but it tends to diminish happiness among some of the language users. |
That proposal don't cover this feature, I think it does not integrate well with this issue.
There are many alternative solution.
to
is simple for a human and it does not require any special support for developer. Custom name requires some effort to find the right function in the entire visibility scope, moreover a specific function must be defined for each type. This is a pro an cons, personally I don't think that this is a great improvement over the current |
Also note about @fvasco example val a: MyType = [ 1, 2, 3 ]
// vs
val a = MyType(1, 2, 3) Funny, it's even shorter |
maybe there should be a counter-keep to remove the cognitive dissonance from having
|
Actually that would have to be a JEP
|
Exhibit A: Nested lists listOf( listOf( listOf( 1, 2 ), listOf( 3, 4 ) ), listOf( listOf( 5, 6 ), listOf( 7, 8 ) ) ) vs. [ [ [1, 2], [3, 4] ],
[ [5, 6], [7, 8] ] ] as List<List<List<Int>>> Exhibit B: Passing Arguments / DSLs val command = command ({ path=listOf("execute", "some", "command")
permissions=setOf("test.command.execute", "test.admin")
options=listOf('c', 'x')
opthelp= mapOf('x' to "Option x", 'c' to "Option c")
}) {
// run on execution
} vs.
As far as I see it, there is not much fundamentally different about this than much of what already add( add( cross( vec1, vec2 ), vec3 ), vec4) As compared to that of, ((vec1 * vec2) + vec3) + vec4 Is, despite being entirely aesthetic, seen by the Kotlin devs as a big enough difference to Since DSLs are extensively used in Kotlin code, that "clutter" seems quite worth it.
Inconsistent and confusing to whom? It would be much, much more confusing to have a collection val list: List<Int> = [ 3, 4, 5 ] Isn't natural? After all, an arbitrary list of objects is inherently ambiguous when it comes val list = [ 1, 2, 3 ] // compiler throws error
println(list) In which case the error should be immediate and easy to debug. And if casting literals is really that bad, we can always just define different notations for different
See the aforementioned examples.
And that is really really annoying from the perspective of anyone who wants to use lists in DSLs Other Arguments
Having two ways to create a list is certainly unsatisfying, but it also is not really enough of an
How does improving concision and expressiveness not serve as motivation?
This can be solved by making e.g.,
Legal, where type becomes determined automatically as in listOf(). Not an amazing solution but a solution nonetheless. The Bottom LineAs I see it, there are two separate issues here:
(1) is questionable. Though there are decisions that have to be made regarding implementation, Regarding (2): I seriously don't see how this feature differs from so many other features already Regarding the argument that the usage case is relatively small: this is really a matter of perspective. Put another way: builder functions are good for everything other than DSLs and nested lists. Collection literals are just good. It would be really nice here to get an official response from Kotlin. |
I thought this keep was an interesting thought exercise and for my own
benefit i started using
_l[ _l[ _l[1, 2], _l[3, 4] ],
_l[ _l[5, 6], _l[7, 8] ] ] as List<List<List<Int>>>
vs.
[ [ [1, 2], [3, 4] ],
[ [5, 6], [7, 8] ] ] as List<List<List<Int>>>
also _a and _s to round out the options.
https://github.com/jnorthrup/columnar/blob/master/vector-like/src/main/kotlin/vec/util/BikeShed.kt#L48
…On Wed, Apr 22, 2020 at 8:51 AM charburgx ***@***.***> wrote:
@Dico200 <https://github.com/Dico200>
I do not see clear benefits of having these literals over the stdlib's
existing builder functions.
Exhibit A: Nested lists
listOf( listOf( listOf( 1, 2 ), listOf( 3, 4 ) ), listOf( listOf( 5, 6 ), listOf( 7, 8 ) ) )
vs.
[ [ [1, 2], [3, 4] ],
[ [5, 6], [7, 8] ] ] as List<List<List<Int>>>
Exhibit B: Passing Arguments / DSLs
val command = command ({ path=listOf("execute", "some", "command")
permissions=setOf("test.command.execute", "test.admin")
options=listOf('c', 'x')
opthelp= mapOf('x' to "Option x", 'c' to "Option c")
}) {
// run on execution
}
vs.
val command = command ({ path=[ "execute", "some", "command" ]
permissions=[ "test.command.execute", "test.admin" ]
options = [ 'c', 'x' ]; opthelp = { 'c': "Option c", 'x': "Option x" }
}) {
// run on execution
}
What benefits are suggested do not outweigh the clutter that this
introduces into the language
...
Being able to pass collection literals as parameters because you don't
have to express the type explicitly then is arguably not such a good reason.
As far as I see it, there is not much fundamentally different about this
than much of what already
exists in Kotlin. E.g., operator overloading. The workflow which is
brought on by code like,
add( add( cross( vec1, vec2 ), vec3 ), vec4)
As compared to that of,
((vec1 * vec2) + vec3) + vec4
Is, despite being entirely aesthetic, seen by the Kotlin devs as a big
enough difference to
constitute making the code more convenient.
Since DSLs are extensively used in Kotlin code, that "clutter" seems quite
worth it.
Casting should not be a way to express what type the programmer wishes a
collection literal to be.
Moreover, casting an expression does not change the type the compiler
expects from that expression (correct me if I'm wrong), and to introduce an
exception to this behavior for collection literals just seems inconsistent
and confusing.
Inconsistent and confusing to whom? It would be much, much more confusing
to have a collection
literal default to any specific kind of collection. What about this:
val list: List<Int> = [ 3, 4, 5 ]
Isn't natural? After all, an arbitrary list of objects is inherently
ambiguous when it comes
to different kinds of collections. The only way I see that this could
cause confusion would be
in a case such as,
val list = [ 1, 2, 3 ] // compiler throws errorprintln(list)
In which case the error should be immediate and easy to debug.
And if casting literals is really that bad, we can always just define
different notations for different
kinds of lists, many implementations for which have been provided. But I
don't think this is necessary.
The current approach is very wordy - Can you give an example?
See the aforementioned examples.
Other modern languages (Python, Groovy, Swift, ECMAScript, etc.) have
collection literals similar to this proposal - and Kotlin has a slightly
more explicit alternative that doesn't use a special syntax.
And that is really really annoying from the perspective of anyone who
wants to use lists in DSLs
or inside of other lists.
Other Arguments
But then there will be two ways to essentially do the same thing
Having two ways to create a list is certainly unsatisfying, but it also is
not really enough of an
argument to dismiss inclusion. After all, is it really better to have no
collection literals at all?
There's no motivation
How does improving concision and expressiveness not serve as motivation?
@hastebrot <https://github.com/hastebrot>
Using type annotations...can get very cumbersome when used in nested
structures
This can be solved by making e.g.,
val list: List = [ 3, 4, 2 ]
Legal, where type becomes determined automatically as in listOf(). Not an
amazing solution but a solution nonetheless.
The Bottom Line
As I see it, there are two separate issues here:
1. Collection literals cannot be implemented satisfactorily in Kotlin
2. Collection literals should not be implemented in Kotlin
(1) is questionable. Though there are decisions that have to be made
regarding implementation,
it is not hard to imagine a world in which collection literals exist in
Kotlin. Moreover,
as has been brought up, collection literals are present in many other
languages. This is pretty
strong evidence that implementation is possible in a reasonable way.
Regarding (2): I seriously don't see how this feature differs from so many
other features already
existing within the language. Operator overloading, delegated properties,
type aliases, scope functions, these all exist for the sole purpose of
making code more concise and/or expressive. Adding collection literals is
just another step in that same goal.
Regarding the argument that the usage case is relatively small: this is
really a matter of perspective.
First of all, DSLs are a massive usage case. Second, If you believe that
people will use listOf() for
everything other than DSLs and nested lists, then I think you are
mistaken. In this way, I would rather argue that *the usage case for
builder functions is too small*.
Put another way: builder functions are good for everything *other than*
DSLs and nested lists. Collection literals are just good.
It would be really nice here to get an official response from Kotlin.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#112 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAR6KQZJJOC5MJUF5I4WCDRNZERHANCNFSM4E7TV3FQ>
.
|
Having literals for array/list, map and set will really help when creating DSLs, so can this bee pushed forward? |
Collection literals are very important for the future of Kotlin and the discussions in this PR were very fruitful. Huge thanks to everybody who had participated. Much love ❤️ However, I'm closing this issue and the corresponding PR in favor of moving the discussion from the subject of a particular design to the discussion of use-cases and requirements that such a design shall meet. So far, the proposed design does not meet all the basic requirements that were identified for support of collection literals in Kotlin. Please, follow the YouTrack issue https://youtrack.jetbrains.com/issue/KT-43871 for further details and cast your votes there 👍 |
Discussion of this proposal:
https://github.com/BenLeggiero/KEEP/blob/collection-literals/proposals/collection-literals.md
Related issue is #113