Search code examples
scalalambdanosuchmethoderrorstructural-typing

Using Structural Type in Scala occurs NoSuchMethodException


I am writing a function with three parameters f, from, to. f should be any object with apply method that consumes and yields an Int.

 def printValues(f: {def apply(n: Int):Int}, from: Int, to: Int) {
   for(i <- from to `to`) print(f(i) + " ")
   print("\n")
 }

I use structural type here to guarantee f has apply() method.

As I invoke the method printValues() with an Array[Int], everything goes well.

printValues(Array(1,1,2,3,5,8,13,21,34,55), 3, 6)

And I try to invoke the method with a lambda expression, the mess comes

printValues((x: Int) => x * x, 3, 6)

error information

java.lang.NoSuchMethodException: ch18.p8.Main$$$Lambda$97/474675244.apply(int)
    at java.lang.Class.getMethod(Class.java:1786)
    at ch18.p8.Main$.reflMethod$Method3(Ch18.scala:270)
    at ch18.p8.Main$.$anonfun$printValues$1(Ch18.scala:270)
    at scala.runtime.java8.JFunction1$mcII$sp.apply(JFunction1$mcII$sp.java:12)
    at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
    at scala.collection.immutable.Range.foreach(Range.scala:156)
    at scala.collection.TraversableLike.map(TraversableLike.scala:234)
    at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
    at scala.collection.AbstractTraversable.map(Traversable.scala:104)
    at ch18.p8.Main$.printValues(Ch18.scala:270)
    at ch18.p8.Main$.delayedEndpoint$ch18$p8$Main$1(Ch18.scala:274)
    at ch18.p8.Main$delayedInit$body.apply(Ch18.scala:267)
    at scala.Function0.apply$mcV$sp(Function0.scala:34)
    at scala.Function0.apply$mcV$sp$(Function0.scala:34)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App.$anonfun$main$1$adapted(App.scala:76)
    at scala.collection.immutable.List.foreach(List.scala:389)
    at scala.App.main(App.scala:76)
    at scala.App.main$(App.scala:74)
    at ch18.p8.Main$.main(Ch18.scala:267)
    at ch18.p8.Main.main(Ch18.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.reflect.internal.util.ScalaClassLoader.$anonfun$run$2(ScalaClassLoader.scala:98)
    at scala.reflect.internal.util.ScalaClassLoader.asContext(ScalaClassLoader.scala:32)
    at scala.reflect.internal.util.ScalaClassLoader.asContext$(ScalaClassLoader.scala:30)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:129)
    at scala.reflect.internal.util.ScalaClassLoader.run(ScalaClassLoader.scala:98)
    at scala.reflect.internal.util.ScalaClassLoader.run$(ScalaClassLoader.scala:90)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:129)
    at scala.tools.nsc.CommonRunner.run(ObjectRunner.scala:22)
    at scala.tools.nsc.CommonRunner.run$(ObjectRunner.scala:21)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.CommonRunner.runAndCatch$(ObjectRunner.scala:28)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:61)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:88)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:99)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:104)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)

I try to verify object lambdaxxx has function apply(In Scala REPL)

scala> val f = (x: Int) => x * x
f: Int => Int = $$Lambda$1020/826690115@7f8633ae

scala> f.apply
   def apply(v1: Int): Int

I got two bug reports:

Seemingly using structural type

A more weird bug demo

So maybe I also fall into this trap.

By the way, If printValues() is below, the lambda expression can fit the method printValues2().

def printValues2(f: (Int) => (Int), from: Int, to: Int) {
  for(i <- from to `to`) print(f(i) + " ")
  print("\n")
}

Thank you for sharing your idea, best wishes.


Solution

  • Just partial explanation of this bug and workaround.

    Structural types are using reflection, so every time you call a method from structural type (like f(i) in your case) you're doing something like f.getClass.getMethod.invoke(i) (so method resolving happens in runtime). Let's simplify your code (2.12.2 REPL):

    scala> val f: {def apply(n: Int):Int} = (x: Int) => x * x
    f: AnyRef{def apply(n: Int): Int} = $$Lambda$1250/571435580@5eb041b5
    
    scala> f(4)
    java.lang.NoSuchMethodException: $$Lambda$1250/571435580.apply(int)
      at java.lang.Class.getMethod(Class.java:1786)
      at .reflMethod$Method1(<console>:13)
      ... 29 elided
    

    And introspect it to see what methods f actually has in runtime

    scala> f.getClass.getMethods
    res21: Array[java.lang.reflect.Method] = Array(public int $$Lambda$1250/571435580.apply$mcII$sp(int), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long)
    
    scala> f.getClass.getMethods()(0)
    res23: java.lang.reflect.Method = public int $$Lambda$1250/571435580.apply$mcII$sp(int)
    

    Here you can easily see that scala (roughly saying) compiled your Int => Int lambda into apply$mcII$sp method, however reflective call is looking for apply(int) method which wasn't added to final byte-code for some reason.

    Here is the workaround:

    scala> val m = f.getClass.getMethods()(0)
    m: java.lang.reflect.Method = public int $$Lambda$1250/571435580.apply$mcII$sp(int)
    
    scala> m.setAccessible(true)
    
    scala> m.invoke(f, new Integer(5))
    res50: Object = 25
    

    You'll probably have to do this as a backup in case f(i) throws an exception.

    However requiring Int => Int is much faster of course :)