Proposal to add Automatic Eta Expansion to the Language


#1

The conversion of methods into functions (eta-expansion) has been improved and happens automatically for methods with one or more parameters.

def m(x: Boolean, y: String)(z: Int): List[Int]
val f1 = m
val f2 = m(true, "abc")

This creates two function values:

f1: (Boolean, String) => Int => List[Int]
f2: Int => List[Int]

The syntax m _ is no longer needed and will be deprecated in the future.

Automatic eta-expansion and nullary methods

Automatic eta expansion does not apply to “nullary” methods that take an empty parameter list.

def next(): T

Given a simple reference to next does not auto-convert to a function.
One has to write explicitly () => next() to achieve that
Once again since the _ is going to be deprecated it’s better to write it this way
rather than next _.

The reason for excluding nullary methods from automatic eta expansion
is that Scala implicitly inserts the () argument, which would
conflict with eta expansion. Automatic () insertion is
limited in Dotty, but the fundamental ambiguity
remains.

More details

Motivation

In Scala, previously, a method reference m was converted to a function value only if the expected type was a function type. If that was not the case, one had to write m _ to force the conversion. For methods with one or more parameters, this restriction has now been dropped reducing the syntactic overhead; previously a responsibility of the programmer. The syntax m _ is no longer needed and will be deprecated in the future.

This proposal is open for discussion in the community and will be discussed in our next SIP meeting, where we’ll take all your feedback into account for the approval/dismissal of this feature.

Related

This SIP proposal is related to the proposal to remote auto-application from the language. For example. A nullary method in this SIP, like next() above, cannot be converted with a simple reference to next. The discussion to remote auto-application from the language is happening here: Proposal to remove auto application from the language


#2

5 posts were split to a new topic: Statically obtain the name of a class member, in a checked way


#3

It seems that I was not enough specific in that question, so I will give it a try once again.
Actually question was about convenient and acceptably-safe way to access member reflection (reflection-like) structures.

In general it could not be reduced to only nameOf macros or MongoDb BSON-based simple query definitions. (Basically most of occasional usage of reflection-like structures could be treated as dirty, but in real life there are some considerable number of use cases when you still prefer to do that "minor dirty things" since using "full scale proper and pure" solution could be treated as over engineering for that cases, but still you may seek for some "sanitation facilities" to make that dirty things little bit more clean)

From other perspective, for now (in Dotty) one may define some macros / inline function in form of extension method and make that access to reflection-like structures little bit more convenient and safe.
Usage of that imaginary reflect macros (defined as (_) reflect (_) extension method) may look like following

   case class Foo(val bar: Int) {
      def baz(arg: Any): String = ???
      object qux {}
   }

   def testFoo() = {
      import reflectMacros.reflect
      import reflectMacros.ReflectType._
      val foo = Foo(1)
      assert("baz" == foo.baz.reflect(METHOD).name)
      assert("bar" == foo.bar.reflect(PROPERTY).name)
      assert("qux" == foo.qux.reflect(OBJECT).name)
   }
Reference definition of that `(_) reflect (_)` extension could look like following
object reflectMacros {
  import scala.quoted._

  // it is not so obvious to encode that `V` should be any function type probably match type could help) ...
  inline def (expr: => V) reflect[V] (reflectType: ReflectType.METHOD.type): ReflectedMethod = ???
  inline def (expr: => V) reflect[V] (reflectType: ReflectType.PROPERTY.type): ReflectedProperty[V] = ???
  inline def (expr: => V) reflect[V] (reflectType: ReflectType.OBJECT.type): ReflectedObject[V] = ???
  // for now overloaded extensions are not supported ...
  // `(_) reflectMethod()` , `(_) reflectProperty()` , `(_) reflectObject()` could be defined instead

  sealed trait ReflectType {
    sealed trait ReflectTypeMethod extends ReflectType
    sealed trait ReflectTypeProperty extends ReflectType
    sealed trait ReflectTypeObject extends ReflectType
  }

  object ReflectType extends ReflectType {
    object METHOD extends ReflectTypeMethod
    object PROPERTY extends ReflectTypeProperty
    object OBJECT extends ReflectTypeObject
  }

  trait ReflectedMember {
    val name: String
    // etc
  }

  trait ReflectedProperty[V] extends ReflectedMember with (() => V) {
    // etc
  }

  trait ReflectedObject[V] extends ReflectedMember with (() => V) {
    // etc
  }

  trait ReflectedMethod extends ReflectedMember { 
    // different versions of `ReflectedMethod ` could be defined to extend proper `Function{K}` traits
    // etc
  }
}

So the question here was, whether it looks reasonable or not to make result of eta-expansion not simply function FunctionN[T1,...,TN,R], but something similar to Kotlin’s KCallable with
FunctionN[T1,...,TN,R] - assuming that this result could be transparently down-casted to FunctionN[T1,...,TN,R] (without performance degradation), but also allowing to down-cast it to some ReflectedMethod[R] and access to that reflection-related stuff easily (in some more safe way).

Or in other words (in terms of assumptive (_) reflect (_) macros, and previous example), as an example, would it become possible to get read of that .reflect(METHOD) for methods and access to method reflection properties just as

  foo.baz.name

instead of writing

  foo.baz.reflect(METHOD).name