Multiple variables/fields in same score with name '_' should be allowed


#1

We can already do this:

for {
  _ <- a()
  _ <- b()
}

or:

x match {
   case _ =>
}

Why not this too?

val _ = a()
val _ = b()

new Foo {
  val _ = a()
  val _ = b()
}

def foo(_ : String): Unit = ()

The behaviour should be the compile generates a unique name for each variable or field so as to avoid conflict.


#2

in the val case, is _ a pattern or a dummy name?

I think it should be a pattern, it would be a good uhh… pattern to work around “statements must return unit” warnings (e.g. from wartremover), meaning if I’m doing something for the side effect but it returns a value that I want to ignore, this looks like an expressive way to write that. It also has a strong parallel to the for-comprehension use case.


#3

I was thinking a dummy name, because the compiler needs to ensure that the object returned does not get garbage collected.


#4

Well, certainly that’s what will be output in the bytecode. But that’s a
completely separate question from what part of the grammar it is.


#5

The val _ = a() thing might be useful, though without wartremover it’s just redundant with a().

What’s the goal of a field you cannot access?

And why would that be—why do you want to avoid GC?


#6

The object referenced by the field may not be able to be accessed directly via this field, but it may be accessed via another reference, for example a WeakReference, in which case the the inaccessible field expresses ownership rather than accessibility.

I think this is the correct behaviour because it is the least surprising. I should be able to fearless modify val _ to val name and vice-versa without changing the behaviour of the code.

Also another case I missed is function arguments that I don’t care about:

def foo(_ : String): Unit = ()


#7

Creating an inaccessible field for the purposes of preventing GC is just so marginal as a use case that I can’t imagine changing the language to accommodate it.

Ditto for allowing inaccessible local variables just to sidestep the value discarding warning. Adding def void[A](a: A) = () might be worthwhile in Predef though.

I can kind of see the case for unnamed parameters since it pops up when you’re implementing a trait, and might be a nice way to document that an argument is unused.


#8

I would argue that it would just be more consistent. Val can be followed by
a pattern. Underscore is a valid pattern. Valid patterns should not be
valid identifiers (otherwise code can seem ambiguous).

Hmm I suppose it might break code that depends on something not being GC’d
(is that a problem?). What’s the current behavior when using an actual
pattern? Like val DummyExtractor(_) = a()


#9

I’ve often wanted this for implicits:

implicit val _ = getStandardDateTimeFormatter

def foo(implicit _: Context) = ...

Naming implicit values and parameters can be redundant - you mostly don’t refer to them by their name again, and their meaning are given by their definition / type.

In addition, it could avoid shadowing problems:

def foo(implicit c: Context) = {
    val a, b, c = 1
    methodThatTakesContextAsImplicitParameter
}

error: could not find implicit value for parameter c: Context

#10

I’ve often wanted this for implicits

Ah ok, yeah that’s a good use case.


#11

In that case, maybe this could be useful too?

implicit def _[A: Decode]: Decode[B] = ???

#12

Names are useful for debugging: “implicit X was used” or “Y is not a valid implicit” are much easier to understand if X and Y can be found in source files.

Regards, Roland


#13

The compiler error might include the source location of the implicit…


#14

Considering the scenarios in which implicits debugging is needed, would you not agree that mentioning only line numbers in the output of -Xlog-implicits would make the output practically useless? Sure, it is possible for the human reader to cross-reference the output with the source file, but making this task harder than it already is seems like the wrong direction to me.

Another very useful feature is the annotation in ScalaIDE (and possibly others) that tells me which implicit got picked up by a given expression—having only a line number there would also render the output a lot less useful.

Neglecting error reporting facilities is a dangerous way to approach type-level computations, it leads to inaccessible code and rejection by most users. Should the goal not be to benefit as many programmers as possible?

Last but not least _ is already overloaded in way too many ways.


#15

I’d say name collision is a bigger problem than not having a name. Even without a name, we still have a type. Most times, I name my implicit declaration after the type anyway, which is the sensible thing to do, since implicits are selected based on their type so the name doesn’t actually give any more information than the type.

In IntelliJ, there is the ability to list which implicits are used in an expression and jump to source. The list could be by type, file and line number.

I’ve started attaching a random hash to all my implicit variables, functions, and classes because there is a high chance of my own EitherOps colliding with someone else’s.


#16

I would hope that it would only be used in a local scope. Certainly, such implicits should not be imported. So I don’t think it’s a dealbreaker.

At least in IntelliJ, from the list I can preview the implementation or jump to it. So again, if used sparingly I think it’s okay.

I think we have to separate implicit vals and implicit defs. For defs that’s certainly valid. However my response would also depend on what you mean by overloaded.

If you mean as far as people’s intuition and increasing the learning curve, I think it’s consistent enough with other places _ is used that the meaning should be obvious. (It would help if we stop telling people _ is highly overloaded. While true in terms of the spec, and important to understand in more non-obvious places like type parameters, I think a better way to teach it is that _ is a wildcard and Scala accepts wildcards in a lot of contexts, whenever it makes sense.)

If you mean in the sense of increasing the footprint of the language grammar or increasing the complexity of the compiler codebase, so for implicit def I agree. For implicit val, maybe it’s naive of me to say this, but it would seem this could be implemented by lifting some restrictions and simplifying the grammar and code. The way I see it, we’re basically saying (1) _ is a pattern not an identifier [should be a simple grammar change that doesn’t affect its size], and (2) an implicit val with a pattern not an identifier, while not assigning to the expression a user-addressable name, it nevertheless exists within the local scope [perhaps because the compiler will assign it a generated name with a private[this] or local-only scope]. Again, while an assumption from ignorance, it seems like it’s just a slight tweak of the rules in a way that makes things more regular.

These two suggestions are entirely orthogonal, though. Well, except that #2 is more intuitive the way things are now, that _ is actually an identifier in the sense that you can’t do two val _s.

Wait, I just did some testing. The current behavior is a bit contradictory.

A. implicit val _ : Int = 10; implicitly[Int] works. This seems to imply that either _ is an identifier, or that #2 is implemented already.
B. implicit def _ : Int = 10 does not compile: identifier expected but '_' found. Why should it work for def but not val? The answer would seem to be that val supports patterns while def does not. So _ is interpreted as a pattern!
C. val _ = 1; val _ = 2 does not compile: _ is already defined as value _ So _ is interpreted as an identifier! (For example, using a tuple2 pattern and annotating 1 and 2 as Any does compile.)

So while the two proposals are orthogonal, #2 is necessary if we want to implement #1 while preserving the behavior of (A). And if we don’t implement #1 – if we say that _ can be an identifier – then in order to remove the above contradiction, we either implement #2 and allow (B) or disallow (A).

In short, the consistent options are:

  • (A) and © compile and work – implement #1 and #2. (B) doesn’t compile because defs can’t use patterns.
  • (A) and (B) compile and work – we don’t implement #1 or #2, instead we say _ is a valid identifier
  • Only © compiles and works – we implement #1 so there’s no redefinition, but without #2 (A) won’t work since 10 is not stored in the scope.

If we would implement #2, it’s not clear what to do for other pattern types. For example a constructor pattern that uses an extractor that returns some “unrelated” value.

All of this doesn’t help for the implicit def case. I’m not sure how important that is. If something needs to be a def, it probably is being reused enough that leaving off the name has no justification. (Usually scala allows shorter forms of things in order to support prototyping, scripting, etc., or syntactic sugar for patterns it encourages.) Besides the generalization – allowing defs to be patterns – has some problems. def (filter, filterNot) ... = partition ... doesn’t seem very efficient ;).


#17

The behaviour right now looks more like a bug than anything:

scala> val _ = 5
_: Int = 5

scala> `_`
res0: Int = 5

scala> def _ = 4
<console>:1: error: identifier expected but '_' found.
       def _ = 4
           ^

scala> def `_` = 4
_: Int

scala> `_`
res1: Int = 4

Disclaimer: you need a 2.10.x REPL for this, but source files containing this code still compile in 2.12.1.


#18

Given that name collisions have been cited upthread as one of the benefits of this idea, I find that hope implausible – I think it’s likely that folks would use it for imports unless that was explicitly outlawed…


#19

It seems like now val _ is putting an identifier named _ in scope. If you create a class with a val _, it will compile to a _ method. Seems like a bug to me.

I think allowing val _ = foo() to create a completely ignored/unreferenceable variable is at least consistent with the way patterns work in other parts of Scala. Putting an “anonymous” implicit in scope for an implicit val _ seems inconsistent, unless we make the rules more complex to do that.


#20

Right. It would require a new rule (or change in the rules?) but without it, fixing the invalid _ identifier could break code. I think if anyone used it, that was their intent.

I’m not advocating for it, just pointing that out.