First draft of collection literals proposal done. ...
# language-proposals
First draft of collection literals proposal done. You can view it on my GitHub here: Tomorrow I will start going over it and refining it to become a final draft before submitting a pull request. Any 🧵 comments here would be greatly appreciated!
👍 3
I’m going to sleep right now, but I will read every comment given 🙂
I'm kind of confused: where does the
operator fun sequenceLiteral
go? Top-level somewhere?
val b2 = [1, 2, 3] as MutableList<Int>
Something tells me this reuse of the
operator will not go well with JB guys
takesAmbiguousType(["1", true, 3] as Set<Any>)
how this is better than
takesAmbiguousType(setOf("1", true, 3))
I think that the lack of clarity in situations
[["foo"] : ["bar", "baz"]]
like this outweighs the risk of mistaking
{ ["foo"] : ["bar", "baz"]  }
for a function
Since a lot of other languages use curly braces for dictionaries and maps, there’s the element that a nice chunk of developers have already been trained to understand the syntax
@karelpeeters Yes, as the document says, “This must be implemented via a top-level function.” I had originally said that it can also go in the companion object of the type it applies to, but there was pushback on that and it would be a little more difficult for the compiler and less elegant to import.
@voddan It’s not a new use. It’s precisely the same as if, today, you write a non-operator version of the
and called it like this:
Copy code
val b2 = syntaxLiteral(1, 2, 3) as MutableList<Int>
@rrader That’s a great point! In that exact scenario it’s not more terse, and one can easily argue it’s not more expressive. But note that to specify another of the ambiguous types, one need only change the type after
. This applies to custom collections and generic types, too. All this while keeping the exact syntax of the sequence the same, something that isn’t really possible with the
@nish Great point! It is a little unclear with all the square brackets flying around. That said, it’s not exactly more clear today with all the parentheses. I will add this to the document, but another reason I chose the brackets is because they always mean “I’m using a collection”. Today,
means “I’m accessing an item in a collection”,
foo[bar] = baz
means “I’m changing an item in a collection”, and
@foo([bar, baz])
means “I’m creating and passing an array collection to this annotation”. I am continuing this pattern by showing that
[ foo : bar, baz : qux ]
mean “I’m creating a dictionary collection”, furthering the enforcement that square brackets mean collections. Additionally, currently, curly braces mean scope. When there is an opening curly brace, you are entering a new scope and can take outer things in, and when there is a closing curly brace, the scope ends and everything local to it is destroyed. Utilizing curly braces as dictionary delimiters muddies this concept, because by definition, everything in that dictionary is persisted and passed somewhere.
Thanks for all the great critique! It’s wonderful to hear 😄
I presume there will be a
operator defined for each of the standard collections, e.g.
. Now consider the operator functions like
on collections that have overloads for different combinations of the first and second parameter types — using collection literals as their parameters would be inherently ambiguous. What overload did I mean in this expression:
collection + [element1, element2]
? Is it
Iterable + Array
, or
Iterable + Iterable
, or
Iterable + Sequence
That’s what I recommend in the Changes to stdlib section: It should basically be a one-to-one replacement/augmentation. Correct, it is ambiguous. It is equally ambiguous as having said, element2))
. As with the entire rest of the language, you will have to differentiate it somehow, be it with
, by saving the collection to an explicitly-typed variable first, etc. I addressed this in the comments of the original gist:
For the same reason you won't be able to call
etc on a collection literal, without casting it first.
If 'as' in this context is simple casting, it ether won't allow optimising for immutable data, or it will crash half the times
@ilya.gorbunov @voddan I don’t think it’s casting… in my head casting is something that happens at runtime. This is typehinting for the compiler.
Also, @ilya.gorbunov, why? I don’t see why
sequenceLiteral("1", "2", "3").map { it.toInt() }
won’t work. Which means
["1", "2", "3"].map { it.toInt() }
would work as well
What is the type of collection literal expression here? Is it List? Is it Sequence?
As stated in the proposal draft, the default type if none is specified would be a List whose generic element type is inferred, just like
@benleggiero From the type inference perspective
[1, 2, 3].map { }
case isn't different from
takesAmbiguousType([1, 2, 3])
and the proposal says the latter should be resolved with casting. It's an interesting idea to default to List/Map even for ambiguous expected types, but it should be examined with care, whether it could introduce unintuitive consequences. Please either treat the ambiguity consistently in the parameter and receiver positions (and decide how should it be treated), or list both options in the "open questions" section of the proposal.
@ilya.gorbunov I would expect
[1,2,3].map {}
would map a
, since that is the default type of a sequence literal. So it would be exactly the same as
listOf(1,2,3).map {}
Maybe I should give examples, but I was imagining that
as an overloaded method, with one taking maybe a Set and another taking maybe an Array (the exact types don't matter as long as none are
), so the default type is not applicable
extension is also overloaded for multiple receiver types (neither of them is List, the closest one is Collection or Iterable)
I understand this. My perspective is that, since
clearly applies to the default type, but the default type doesn't apply to
, this is why casting and other type hints aren't necessary in the map example, but are in the ambiguity example. I'll note this in the proposal
But that breaks the symmetry between a receiver parameter and normal parameter, right?
I am on my phone, but when I get back I'll write an example that will hopefully clear this up
Here is a better illustration of what I am going for:
Copy code
fun takesAmbiguousType(x: Set<Int>) {}
fun takesAmbiguousType(x: Array<Int>) {}

takesAmbiguousType([1, 2, 3]) // Error: cannot resolve type; default sequence literal type List<Int> does not match any candidates
takesAmbiguousType([1, 2, 3] as Set<Int>) // OK
takesAmbiguousType([1, 2, 3] as Array<Int>) // OK

val y: Set<Int> = [1, 2, 3]
takesAmbiguousType(y) // OK

// Defined in stdlib: public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> 

public inline fun <T, R> map2(x: Iterable<T>, transform: (T) -> R): List<R> { /* ... */ }

val z = [1, 2, 3] // Inferred type is List<Int> { it.toString() } // Maps List<Int> to List<String>
map2(z) { it.toString() } // Maps List<Int> to List<String>

[1, 2, 3].map { it.toString() } // Maps List<Int> to List<String>; no type is given but default type List<Int> matches a map candidate
map2([1, 2, 3]) { it.toString() } // Maps List<Int> to List<String>; no type is given but default type List<Int> matches a map2 candidate
Note that
has a couple of overloads, e.g. with
as receivers. Does it change the resolution result in the example above? If it doesn't, consider the following overloads:
Copy code
fooAnyOrSet(param: Any)
fooAnyOrSet(param: Set<*>)

fooAnyOrSet([1, 2, 3])
Is it an expected behavior that the first overload is selected in this case?
recommends that these existing functions be marked as deprecated
I am absolutely against such proposal about deprecation of existing builders, it’s not clear how this change can improve language and no clear why we should do that, for example not clear what to do with method references to those methods
@orangy I would expect that to pass a
fun fooAnyOrSet(param: Any)
, since the default type matches it
@gildor That’s fine. It wasn’t a sticking point for me, which is why I used the word “recommend”. That said, what do you mean “method references to those methods”? Does deprecation affect those at all?
Imagine such code:
Copy code
val list: List<String> = "some string".run(::listOf)
How can I do that without builder functions using only literals? If builders would be deprecated this code will be marked as deprecated too
That’s a good point. One day I’d like to see a way to pass constructors as function references… maybe that’s not possible on the JVM without weird wrapping workarounds
You can get a function reference to a constructor even now:
Copy code, ::Pair)
// or
K 1
🤩 1
Problem with constuctor, that List doesn’t have one, it’s an interface. And
hides implementation details, so you cannot do something like
, only
, but it’s not what you wait from list builder
👍🏼 1
"some string".run(::[])
🙈 3
😄 1
👆 totally not like Scala scala 🧌
😂 1
@benleggiero I think your draft and these comments have tremendous value, either paving the way for construction literals, or demonstrating all the problems with the feature. Regardless of the outcome, I think we need to preserve the draft and the discussion for future use. Unfortunately the discussion happened on Slack, not on GitHub (as it should have been, according to KEEP rules). Could you please go for the trouble of publishing your draft to KEEP repo (all of its major iterations) and the comments (at least the major points in the discussion). I may offer my help with the routine tasks or editing.
💯 1
@benleggiero Kind reminder that this thread will soon vanish under message event horizon, so if you want to evacuate something useful from it to KEEP, it's time!
Thanks, @ilya.gorbunov! Archived here:
@benleggiero on Imgur it is not text, thus it is not searchable, making it useless. I strongly recommend saving all to Github.
@voddan Sure thing, when I have time. That's the quick thing I could do while mobile. Not much time at desktop these days.