Skip to content
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

Closed
wants to merge 1 commit into from

Conversation

KyLeggiero
Copy link

@KyLeggiero KyLeggiero commented May 13, 2018

@udalov
Copy link
Member

udalov commented May 15, 2018

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:

val a1: List<String> = ["a", "b", "c"]
val a2: Array<String> = ["a", "b", "c"]

We should add at least the following declarations:

operator fun <T> sequenceLiteral(vararg elements: T): Array<T> = ...
operator fun <T> sequenceLiteral(vararg elements: T): List<T> = ...

But these are conflicting overloads and Kotlin does not allow that.

@ilya-g
Copy link
Member

ilya-g commented May 21, 2018

@udalov I believe this proposal implies allowing these conflicting overloads for sequenceLiteral operator.

@KyLeggiero
Copy link
Author

Great observation, @udalov. I only found that issue late into the final draft.
@ilya-g is correct. There might also have to be a second KEEP for resolution of such overloads, but... I am not a bytecode engineer so I'm not sure how that would work under the covers. I would need help from more experienced JVM engineers.

@helmbold
Copy link

helmbold commented May 22, 2018

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 List a "first-class citizen" and Set not?

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 collectionOf methods in Kotlin. From my point of view this is pure beauty, very readable, and in the best sense of a "scalable" language. I've never heard anyone complaining about the "verbosity".

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.

@accursoft
Copy link

Collection literals may be more relevant for passing as parameters. For example, if you have
fun foo(list: List<Int>) = ...
You could call it with foo([1, 2, 3]).
This is particularly relevant for DSLs. For example, listOf and mapOf can make Gradle scripts somewhat ugly when translating from Groovy to Kotlin.

@helmbold
Copy link

@accursoft If collections literals are used for passing them as parameters, one could pass the collection elements as vararg, at least if there is only one such parameter in a method call. Otherwise it would possible to use a builder.

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

  • a is a List
  • b is a Set
  • c is a Map

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.

@gildor
Copy link
Contributor

gildor commented May 23, 2018

@helmbold You right, but if you want to pass existing collection to vararg method it looks not so nice:
SomethingComplex.a(*someA.toTypedArray()).b(*someB.toTypedArray()).c(*someC.toTypedArray()).build()

instead of

SomethingComplex.a(someA).b(someB).c(someC).build()

Also vararg copies array on a method call.
So the solution with vararg is not so universal

@helmbold
Copy link

helmbold commented May 23, 2018

@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 List(1, 2, 3) instead of listOf(1, 2, 3). It would look so much better, even if it wouldn't be the Kotlin Way ™.

@ilya-g
Copy link
Member

ilya-g commented May 23, 2018

@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.

@KyLeggiero
Copy link
Author

Archived Slack discussion of first draft: https://imgur.com/a/NKw0MCF

@ilya-g
Copy link
Member

ilya-g commented May 23, 2018

What this proposal currently lacks is the analysis of situations when there are multiple sequenceLiteral functions applicable and the rules how to choose one of them or report an ambiguity.

Examples are:

  • [a, b] + [c, d] — how to choose between various overloads of plus operator
  • fun takesSet(Set<T>) — how to choose between sequenceLiteral overloads proposed by this KEEP:
    • sequenceLiteral(...): Set<T>
    • sequenceLiteral(...): HashSet<T>
    • sequenceLiteral(...): LinkedHashSet<T>

@accursoft
Copy link

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 List and Map, <10% of the proposed implementation.

@gildor
Copy link
Contributor

gildor commented May 24, 2018

@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).
Also array literals have own poblems with ambiguous types:
val a = [1,2,3]
What is type of a? IntArray or Array[Int]?

@gildor
Copy link
Contributor

gildor commented May 24, 2018

Why is List a "first-class citizen" and Set not?

@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)

@accursoft
Copy link

@gildor I'm proposing that [a,b,c] creates a List, the same as listOf(a,b,c) would. I only mentioned arrays in the context of having one hard-coded type for collection literals. Arrays in annotations, Lists in code (and perhaps Maps for dictionary literals).

Not completely consistent, but highly pragmatic.

@fvasco
Copy link

fvasco commented May 24, 2018

I agree with @accursoft, data structure type is important and sequenceLiteral operator hides this kind of information.

Moreover the "Motivation" section should be revisited

  • "The current approach is very wordy" is a personal opinion and I do not agree with it.

  • "The current approach does not leverage the powerful type inference features of Kotlin" maybe, but the following example does look wordy and lacks of type inference

    val myHashMap: HashMap<String, List> = ["Foo": ["Bar", "Baz"]]

  • "Other modern languages have collection literals similar to this proposal" ???

  • "Kotlin currently supports identical sequence literal syntax in the arguments of annotations" yes but its behaviour is deterministic

  • "This would provide Kotlin library writers a standard, more expressive way to allow their users to instantiate custom collection types" this paragraph should be shift out the motivation list

Having a cool synstax to define read-only, random-access list is pratical, same for maps.

Instead having

takesAmbiguousType(["1", true, 3] as Set<Any>)

working and

val list = ["1", true, 3] 
takesAmbiguousType(list as Set<Any>)

not working looks strange, IMHO.

@helmbold
Copy link

helmbold commented May 24, 2018

Let's say we could settle with parenthesis for all collection literals (a, b, c) and that we would distinguish the actual types with a prefix what would syntactically be similar to Scala string interpolation, for example:

s"Hello, $name"
f"$name%s is $height%2.2f meters tall"

... then we would have the syntax I suggested above (s(1, 2, 3) for sets, m("a" to 1, "b" to 2) for maps and so on). I just want to show with this Scala example that we are possibly discussing an overly complex solution for an actually small problem.

@voddan
Copy link
Contributor

voddan commented May 25, 2018

Why is List a "first-class citizen" and Set not?

@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 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 [] syntax. Such APIs are confusing, inflexible, and a pain to work with. I would not want that disease for Kotlin!

@fvasco
Copy link

fvasco commented May 27, 2018

Some considerations:

[1, 2, 3] as Set<Int>

has same performance of

[1, 2, 3].toSet()

so we consider

val s1 = [1, 2, 3] as Set<Int> // deny
val s2 = [1, 2, 3].toSet() // allow
val s3 : Set<Int> = [1, 2, 3] // allow

As alternative using "prefix dictionaries with a # symbol" is a "natural way to distinguish a literal for a List<Pair<*, *>> from a Map<*, *>"

@fvasco
Copy link

fvasco commented May 27, 2018

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 sequenceLiteral operator to of, ie:

operator fun <T> Set.Companion.of(vararg elements: T): Set<T> = ..

this allows replacing

val set = ([ "a", "b", "c"] as Set<String>).map { transform(it) }

to

val set = Set.of( "a", "b", "c").map { transform(it) }

@gildor
Copy link
Contributor

gildor commented May 27, 2018

@fvasco There is a problem with this approach:
You cannot implement it for a collection without existing companion object, so it's just impossible to add collection literal syntax for a Java class or for a Kotlin class without companion object

@fvasco
Copy link

fvasco commented May 27, 2018

Hi @gildor,
Kotlin 1.2 does not support this syntax, right.
However I cannot understand your opinion about this way to solve the question.

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 operator companion fun <T> Set.of(vararg elements: T): Set<T> = .. extension function)?

Do we consider some alternative way to current proposal, ie: considering the syntax

val a : MyType = [ 1, 2, 3]

as syntactically equivalent of

val a : MyType = MyType(1, 2, 3)

so it is possible to write

fun MyType(vararg ints : Int) : MyType = TODO()

// or

class MyType(vararg ints : Int) { ... }

// or

class MyType {

  companion {

    operator fun invoke(vararg ints : Int) : MyType = TODO()

  }

}

note: this last proposal allows the follow syntax

data class Point(val x: Int, val y: Int)

fun distance(p1: Point, p2: Point) = ...

val delta = distance([2, 3], [4, 5])

without adding more code

@zxj5470
Copy link

zxj5470 commented Jun 14, 2018

Enable type hint, the type of s is Array
image
And I think specify type is OK such as
val s:IntArray = [1,2,3] or val s:List<Int> = [1,2,3] with implicit cast

@hastebrot
Copy link

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]

@ilya-g
Copy link
Member

ilya-g commented Jun 14, 2018

@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.

@helmbold
Copy link

Oracle had planned to introduce collection literals in Java 8, but dropped the idea. The arguments are valid for Kotlin as well.

@bennylut
Copy link

bennylut commented Jun 28, 2018

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 sequenceLiteral function. Do you have any plans to "inline" the function in some way to avoid this performance overhead?

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)

@KyLeggiero
Copy link
Author

@jnorthrup
bon voyage literals!

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.

@Tienisto
Copy link

Any progress on this?
I do not want to write listOf(listOf(listOf(...,...),...))) to implement some test data or constants.
Even Java does it right...

@Longhanks
Copy link

Major +1 on this - listOf, arrayOf etc. feel very clumsy coming from Swift. Even Objective C retroactively introduced collection literals (https://en.wikipedia.org/wiki/Objective-C#Literals).

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.

@jnorthrup
Copy link

jnorthrup commented Jan 21, 2020

so where does this thread end?

image

object Ω 
operator  fun<T> Ω.get(vararg t:T): List<T> = t.map { it  }
val strings=Ω["abc","dfe","fge"]

@Dico200
Copy link

Dico200 commented Jan 21, 2020

@Longhanks
Can you please clarify your argument? Why do you think it would have similar improvements on readability for Kotlin? I am not an Objective-C developer, but going by your example, I would argue that Kotlin's present collection constructors are way more readable than the your Objective-C ones without collection literals. Therefore the difference in readability for Kotlin would be smaller. I would also argue that Kotlin's present collection constructors are more readable than your Objective-C's collection literals. As you know the equivalent in Kotlin would be:

val myArray = arrayOf(object1, object2, object3)
val myMap = mapOf(key to someObject)

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.

@Longhanks
Copy link

@Dico200

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. [] and :) during quickly scanning code than it is to scan ASCII only function names such as mutableListOf(), mutableSetOf() and the others.

@gbaldeck
Copy link

gbaldeck commented Jan 24, 2020

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 {"a", "b"} is what is used in Dart, and it works well IMO.

@Dico200
Copy link

Dico200 commented Jan 24, 2020

@Longhanks perhaps it would help if the IDE highlighted some of the elements. Like the parentheses and the to keyword? I have the impression people often differ in the order, way and speed with which they read characters, which might well be central to our difference here, and I have not taken that into account in my own considerations about proposals like this one.

@Longhanks
Copy link

@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)?

@Dico200
Copy link

Dico200 commented Jan 24, 2020

@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.

@KyLeggiero
Copy link
Author

Re: @gbaldeck

The set syntax {"a", "b"} is what is used in Dart, and it works well IMO.

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.

@jnorthrup
Copy link

jnorthrup commented Mar 2, 2020

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.

/**
 * missing stdlib list operator https://github.com/Kotlin/KEEP/pull/112
 */
object _l {
    operator fun<T> get(vararg t:T)= listOf (*t)
}

/**
 * missing stdlib array operator https://github.com/Kotlin/KEEP/pull/112
 */
object _a {
    operator fun<T> get(vararg t:T)=   t
}

/**
 * missing stdlib set operator https://github.com/Kotlin/KEEP/pull/112
 */
object _s {
    operator fun<T> get(vararg t:T)=   setOf (*t)
}

@gildor
Copy link
Contributor

gildor commented Mar 2, 2020

jetbrains doesn't like @BenLeggiero who proposed it

@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.
If you saying about some particular people in JB, then it looks very rude to blame anyone and say that a person is biased against any other particular person, at least without serious proofs.

because anything is wrong with the proposal

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

@jnorthrup
Copy link

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:

  1. lack of any Kotlin key player who cares, which is by net positive gain about the same as disliking the author.
  2. lack of a working compiler level PR

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.

@jnorthrup
Copy link

jnorthrup commented Mar 2, 2020

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.

@Dico200
Copy link

Dico200 commented Mar 2, 2020

I don't see alot of compelling arguments against this KEEP, I see "kotlin experts" bikeshedding.

@jnorthrup
There don't need to be compelling arguments against this KEEP when there is little concrete motivation in favor of it in the proposal in the first place, as I showed here (this touches upon all the motivating arguments in the proposal). I think the proposal is simply lacking a compelling argument in favor of implementing it. Without a decent motivation, this proposal cannot move forward.

@AarjavP
Copy link

AarjavP commented Mar 2, 2020

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:

  • Only allowing collection literals where the type is known. I believe this is in line with kotlin's spirit of type safety. See janvladimirmostert's example. This will not allow using collection literals with operators like in [1, 2] + [2, 4], but I that might be perfectly fine and maybe even for the better.
  • Nested collections are really where collection literals can shine for both readability and reuse/productivity. For example if we have a list of coordinates List<List<Int>> (we can get back to missing Point class later), the current approach would look like listOf(listOf(1, 2), listOf(1, 2), ..). This may or may not be an issue depending on the 'platform'/'ecosystem' the code is being written for. For those using kotlin data science notebooks / scripts I think it would be much more noticeable than some other 'back end' code. See Tienisto's similar example.
  • Map literals are tricky. Using { has pros / cons. Using : has pros / cons. Using intermediate Pair object like we currently have also has pros / cons.

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 val personB: Person = [...personA, firstName= "Joe"]

@fvasco
Copy link

fvasco commented Mar 2, 2020

Hi, @AarjavP,
I proposed something some time ago...

@AarjavP
Copy link

AarjavP commented Mar 2, 2020

Sorry I missed it, I like it!
Do you have any other ideas on how to tackle operator declaration issue?
I do like the companion object approach. Similarly, we can also consider using the already existing constructors.

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?

@gbaldeck
Copy link

gbaldeck commented Mar 3, 2020

@BenLeggiero

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.

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.

@jnorthrup
Copy link

jnorthrup commented Mar 3, 2020

@Dico200

There don't need to be compelling arguments against this KEEP when there is little concrete motivation in favor of it in the proposal in the first place

concrete applies to almost nothing in language design except perhaps a metric of brevity, and in the matter of [a] vs. listOf(a) we have managed to contrive an almost meaningless proof against a completely meaningless counter-point.

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.

@fvasco
Copy link

fvasco commented Mar 3, 2020

Do you have any other ideas on how to tackle operator declaration issue?

That proposal don't cover this feature, I think it does not integrate well with this issue.
Primarily, we have to consider if this is a issue to solve, so if it make sense to write [1, 2] [2, 4] instead of [1, 2, 2, 4].

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?

There are many alternative solution.
I focused on simplicity and readability, so to translate

val a : MyType = [ 1, 2, 3 ]

to

val a : MyType = MyType(1, 2, 3)

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 listOf, setOf, etc...

@gildor
Copy link
Contributor

gildor commented Mar 3, 2020

Also note about @fvasco example
It would be fair to compare with inferred version:

val a: MyType = [ 1, 2, 3 ]
// vs
val a = MyType(1, 2, 3)

Funny, it's even shorter

@jnorthrup
Copy link

maybe there should be a counter-keep to remove the cognitive dissonance from having

listOf(1,2,3,4).toString()=="[1, 2, 3, 4]"

@JakeWharton
Copy link

Actually that would have to be a JEP

jshell> List.of(1, 2, 3, 4).toString()
$1 ==> "[1, 2, 3, 4]"

@mxsdev
Copy link

mxsdev commented Apr 22, 2020

@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 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
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

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.

@jnorthrup
Copy link

jnorthrup commented Apr 22, 2020 via email

@sirinath
Copy link

sirinath commented Sep 3, 2020

Having literals for array/list, map and set will really help when creating DSLs, so can this bee pushed forward?

@elizarov
Copy link
Contributor

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 👍

@elizarov elizarov closed this Dec 10, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.