Is a lambda function also an object with the sign of Function1?

object MyApp { def printValues(f: {def apply(x: Int): Int}, from: Int, to: Int): Unit = { println( (from to to).map(f(_)).mkString(" ") ) } def main(args: Array[String]): Unit = { val anonfun1 = new Function1[Int, Int] { final def apply(x: Int): Int = x * x } val fun1 = (x:Int)=>x*x printValues(fun1, 3, 6) } } 

I thought that the lambda function in scala is also an object that extends the trait Function1. However, this code does not work for printValues(fun1, 3, 6) , not printlnValues(anonfun1, 3, 6) . Why is this so?

+5
source share
3 answers

This was a very interesting question to study. There is a saying that you should never rely on the details of the implementation of the code, I think this borders on this.

Try and break what is happening here.

Structural Types:

If you need a structural type, for example, you did this:

 def printValues(f: {def apply(x: Int): Int}, from: Int, to: Int): Unit = { println( (from to to).map(f(_)).mkString(" ") ) } 

What Scala means is the use of reflection to try to find the apply method at run time and call it dynamically. It transforms into something similar to this:

 public static Method reflMethod$Method1(final Class x$1) { MethodCache methodCache1 = Tests$$anonfun$printValues$1.reflPoly$Cache1.get(); if (methodCache1 == null) { methodCache1 = (MethodCache)new EmptyMethodCache(); Tests$$anonfun$printValues$1.reflPoly$Cache1 = new SoftReference((T)methodCache1); } Method method1 = methodCache1.find(x$1); if (method1 != null) { return method1; } method1 = ScalaRunTime$.MODULE$.ensureAccessible(x$1.getMethod("apply", (Class[])Tests$$anonfun$printValues$1.reflParams$Cache1)); Tests$$anonfun$printValues$1.reflPoly$Cache1 = new SoftReference((T)methodCache1.add(x$1, method1)); return method1; } 

This is the decompiled Java code that is emitted. In short, he is looking for the apply method.

What happens in Scala <= 2.11

For any version of Scala prior to 2.12, declaring an anonymous function results in a compiler generating a class extending AbstractFunction* , where * is the arity of the function. These abstract function classes in turn inherit Function* and implement their apply method with lambda implementation.

So, for example, if we take your expression:

 val fun1 = (x:Int) => x * x 

The compiler emits for us:

 val fun2: Int => Int = { @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1$mcII$sp with Serializable { def <init>(): <$anon: Int => Int> = { $anonfun.super.<init>(); () }; final def apply(x: Int): Int = $anonfun.this.apply$mcII$sp(x); <specialized> def apply$mcII$sp(x: Int): Int = x.*(x) }; (new <$anon: Int => Int>(): Int => Int) }; () 

When we look at the bytecode level, we see the generated anonymous class and apply methods:

 Compiled from "Tests.scala" public final class othertests.Tests$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable { public static final long serialVersionUID; public final int apply(int); Code: 0: aload_0 1: iload_1 2: invokevirtual #21 // Method apply$mcII$sp:(I)I 5: ireturn public int apply$mcII$sp(int); Code: 0: iload_1 1: iload_1 2: imul 3: ireturn So what happens when you request the `apply` method at runtime? The run-time will see that theirs a method defined on `$anonfun` called `apply` which takes an `Int` and returns an `Int`, which is exactly what we want and invoke it. All is good and everyone happy. 

Lo and Behold, Scala 2.12

In Scala 2.12, we get something called a SAM transform. SAM types is a Java 8 feature that allows you to shorten the implementation of an interface and instead provide a lambda expression. For instance:

 new Thread(() -> System.out.println("Yay in lambda!")).start(); 

Instead of implementing Runnable and overriding public void run . Scala 2.12 sets the goal of being compatible with SAM types through SAM conversion, whenever possible.

In our particular case, the SAM conversion is possible, which means that instead of Scala Function1[Int, Int] we get a specialized version of scala.runtime.java8.JFunction1$mcII$sp . This JFunction compatible with Java and has the following structure:

 package scala.runtime.java8; @FunctionalInterface public interface JFunction1$mcII$sp extends scala.Function1, java.io.Serializable { int apply$mcII$sp(int v1); default Object apply(Object t) { return scala.runtime.BoxesRunTime.boxToInteger(apply$mcII$sp(scala.runtime.BoxesRunTime.unboxToInt(t))); } } 

This JFunction1 was specialized (for example, we use @specialized annotation in Scala) to emit a special method for def apply(i: Int): Int . Note one important factor here that this method only implements the apply method of the form Object => Object , not Int => Int . Now we can begin to understand why this can be problematic.

Now, when we compile the same example in Scala 2.12, we see:

 def main(args: Array[String]): Unit = { val fun2: Int => Int = { final <artifact> def $anonfun$main(x: Int): Int = x.*(x); ((x: Int) => $anonfun$main(x)) }; () 

We no longer see a method extending AbstractFunction* , we just see a method called $anonfun$main . When we look at the generated bytecode, we see that inside it calls JFunction1$mcII$sp.apply$mcII$sp(int v1); :

 public void main(java.lang.String[]); Code: 0: invokedynamic #41, 0 // InvokeDynamic #0:apply$mcII$sp: ()Lscala/runtime/java8/JFunction1$mcII$sp; 5: astore_2 6: return 

However, if we explicitly extend Function1 ourselves and implement apply , we get a similar behavior with the previous version of Scala, but not exactly the same:

 def main(args: Array[String]): Unit = { val anonfun1: Int => Int = { final class $anon extends AnyRef with Int => Int { def <init>(): <$anon: Int => Int> = { $anon.super.<init>(); () }; final def apply(x: Int): Int = x.*(x) }; new $anon() }; { () } } 

We no longer extend AbstractFunction* , but we have an apply method that satisfies a condition of a structural type at runtime. At the bytecode level, we see int apply(int) , a object apply(object) and many cases for the @specialization attribute, annotating Function* :

 public final int apply(int); Code: 0: aload_0 1: iload_1 2: invokevirtual #183 // Method apply$mcII$sp:(I)I 5: ireturn public int apply$mcII$sp(int); Code: 0: iload_1 1: iload_1 2: imul 3: ireturn public final java.lang.Object apply(java.lang.Object); Code: 0: aload_0 1: aload_1 2: invokestatic #190 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I 5: invokevirtual #192 // Method apply:(I)I 8: invokestatic #196 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer; 11: areturn 

Output:

We see that the implementation details show how the Scala compiler processes lambda expressions in some circumstances. This is mistake? my feelings tend to no. In any case, the Scala specification guarantees that there must be a method called apply that matches the signature of the lambda, so we call this an implementation detail. Although this is a really interesting quirk, I would not rely on such code in any production environment, as it can be changed.

+7
source

I think that I do not fully understand what the problem is. Your code also works.

But the line

I thought the lambda function in scala is also an object that extends Function1

is correct if scala version is <2.12

The function literal in scala is simply FunctionN syntactic sugar, which are Function1 to Function22. So the code below is exactly the same.

 val f: Int=> Int = new Function1[Int, Int] { def apply(x:Int): Int = x * x } val f2: Int => Int = (x: Int) => x * x 

If you want to verify that both of them work as FunctionN, your printValues code should be like this

 def printValues(f: (Int) => Int, from: Int, to: Int): Unit = { println((from to to).map(f).mkString(" ")) } //or def printValues(f: Function1[Int,Int], from: Int, to: Int): Unit = { println((from to to).map(f).mkString(" ")) } 

And again, due to syntactic sugar, they have the same meaning. Your definition of the parameter type AnyRef{def apply(x: Int): Int} So

 object Foo { def apply(x: Int): Int = x * x } 

can be used for printValues .

If you are working on scala 2.12, this is a bit of a different story.

If your function literal satisfies some conditions, it is automatically converted to a SAM type. See sam-conversion for more details.

EDITED You should use 2.12 because the code works fine if it is on 2.11 or in the headphones. as I mentioned in the last, this is all due to the conversion of sam. fix printValues parameter in (Int) => Int or Function1[Int,Int] to explicitly define FunctionN

+2
source

A method call on an instance of a structural type involves reflection at run time.

For val f: { def apply(i: Int): Int} = (x: Int) => x * x calling f(10) (or f.apply(10) ) is allowed to something like f.getClass.getMethod("apply", classOf[Int]).invoke(f, 10) .

And lambda has such a method in Scala 2.11.8 (and Function1 still has it in 2.12.1, so your code works fine with anonfun1 ):

 scala> val f: { def apply(i: Int): Int } = (x: Int) => x * x f: AnyRef{def apply(i: Int): Int} = <function1> scala> f.getClass.getMethods.filter(_.getName == "apply") foreach println public final int $line11.$read$$iw$$iw$$anonfun$1.apply(int) public final java.lang.Object $line11.$read$$iw$$iw$$anonfun$1.apply(java.lang.Object) 

But in 2.12.1, it only has a method that accepts Object :

 scala> val f: { def apply(i: Int): Int } = (x: Int) => x * x f: AnyRef{def apply(i: Int): Int} = $$Lambda$1015/ 1912769093@364b1061 scala> f.getClass.getMethods.filter(_.getName == "apply") foreach println public default java.lang.Object scala.runtime.java8.JFunction1$mcII$sp.apply(java.lang.Object) 

It can still be called, of course:

 scala> f.getClass.getMethod("apply", classOf[AnyRef]).invoke(f, Int.box(10)).asInstanceOf[Int] res2: Int = 100 

But it does not work with the standard method call of a structural type.


I believe this is a bug in the Scala standard library. Lambdas in 2.12.1 are presented with examples of the scala.runtime.java8.JFunction* classes. For instance:

 @FunctionalInterface public interface JFunction1$mcDF$sp extends scala.Function1, java.io.Serializable { double apply$mcDF$sp(float v1); default Object apply(Object t) { return scala.runtime.BoxesRunTime.boxToDouble(apply$mcDF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))); } } 

And I see no reason why they do not have a specialized method similar to the one created for scala.Function* :

 @FunctionalInterface public interface JFunction1$mcDF$sp extends scala.Function1, java.io.Serializable { double apply$mcDF$sp(float v1); default Object apply(Object t) { return scala.runtime.BoxesRunTime.boxToDouble(apply$mcDF$sp(scala.runtime.BoxesRunTime.unboxToFloat(t))); } default double apply(float t) { return apply$mcDF$sp(t); } } 
+2
source

Source: https://habr.com/ru/post/1261477/


All Articles