When anonfun$1 becomes anonfun$m1$1 (or vice versa) in Scala?

2.5k views Asked by At

Can anyone explain why Scala gives two different names in the following cases? Why couldn't Scala give the same names in each case?! Is there some kind of consistency I don't know yet? It must be related to eta-expansion, right?

object A {
  val ms: Seq[String => String] = Seq(m1)
  def m1(s: String) = s
}
A.ms.map(m => m.getClass.getSimpleName)

The above gives List(anonfun$1) - note the name of the element - anonfun$1 while the following gives anonfun$m1$1. Why?

object A {
  val ms: Seq[String => String] = Seq(m1)
  def m1: String => String = s => s
}
A.ms.map(m => m.getClass.getSimpleName)

Could I also ask for a simpler case to demonstrate the difference (perhaps without using Seq)?

2

There are 2 answers

0
dmitry On BEST ANSWER

It seems compiler adds path into name when creates anonymous classes. Your example (pretty interesting in some other sense by the way) may be simplified down to:

def m1(s: String) = s
def m2: String => String = s => s
val ss: Seq[String => String] = Seq(m1, m2)
ss map (_.getClass.getSimpleName)

Which produces:

res28: Seq[String] = List(anonfun$1, anonfun$m2$1)

Without Seq:

(m1 _).getClass.getSimpleName
res34: String = anonfun$1

m2.getClass.getSimpleName
res35: String = anonfun$m2$1

Luckily m1 in Seq is equivalent to m1 _, and m2 is equivalent to method application. What about names - compiler adds path to autogenerated classes, so, top-scope m1 turns to anonfun$1 (anonfun is default prefix for generated classes for functions), and because m2 returns function from inside, that function gets one more element in path (name).

Interestingly, this weird thing:

def a() = {
  def b() = {
    def c() = {
      def d(): String => String = s => s
      d()
    }
    c()
  }
  b()
}

Has name:

a().getClass.getSimpleName
res30: String = anonfun$d$1$1

So, no trace of a, b, c! So, it's somewhat complicated, I tried but couldn't have found the exact choice and patterns of naming in compiler source, though reading was interesting.

0
som-snytt On

You can observe symbol manipulation:

$ scala -Yshow-syms -uniqid -Dscala.repl.maxprintstring=8000

In the REPL, be forewarned, you'll see one ream of output for your wrapped code and then a second ream of output for the bit of code that prints your result.

scala> def m1: String => String = s => s

[[symbol layout at end of parser]]
* package scala#22 (final)

[[symbol layout at end of namer]]
* object $read#58183
* package $line6#58181 (final)
  package scala#22 (final)

[[symbol layout at end of packageobjects]]
  object $read#58183
  package $line6#58181 (final)
  package scala#22 (final)

[[symbol layout at end of typer]]
* class String#643 (final)
* constructor Object#3505
* object $read#58184
*     constructor $read#58188
*     object $iw#58190
*         constructor $iw#58192
*         object $iw#58193
*         object $iw#58194
*             constructor $iw#58196
*             method m1#58197
*                 value $anonfun#58199 (<synthetic>)
*                     value s#58200

...massive snip...

          object $iw#58194
              constructor $iw#58196
              method m1#58197
                  <$anon: Function1#2093> (final <synthetic>)
                      constructor $anonfun#58218


[[symbol layout at end of lambdalift]]

...snip...

          object $iw#58194
O             <$anon: Function1#2093> [Owner was method m1#58197, now object $iw#58194] (final <synthetic>)
                  constructor $anonfun$m1$1#58218

As the output says, the anonfun becomes a child of the enclosing class because it is implemented as a class; any captured variables are passed to its constructor.

A quick look at LambdaLift.scala shows that newName actually special-cases anonymous functions that are owned by methods, to have the name-mangling you've pointed out.

This is an easy way to avoid naming collisions such as:

scala> class Foo { def x = 1 to 10 map (2 * _) ; def y = 1 to 10 map (3 * _) filter (_ > 6) }

But since newName is getting a fresh name anyway, I would guess that retaining the method name is a debugging aid.

Is it a good debugging aid?

Multiple anonfuns in any method 'm' in a compilation unit will all be named anonfun$m$1 and so on; there is no way to distinguish whether the anonfun$m$3 belonged to Foo.m or Bar.m, except by inspecting those classes.

I would rely on the REPL to discover anonfuns for me, except currently it is no smarter than we are.