gpt4 book ai didi

scala - lambda 函数不也是具有 Function1 特征的对象吗?

转载 作者:行者123 更新时间:2023-12-04 13:37:10 28 4
gpt4 key购买 nike

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)
}
}

我认为 scala 中的 lambda 函数也是扩展 Function1 特征的对象。但是,此代码对于 printValues(fun1, 3, 6) 失败而不是 printlnValues(anonfun1, 3, 6) .为什么呢?

最佳答案

这是一个非常有趣的问题。有一种说法是永远不应该依赖代码中的实现细节,我认为这是这样做的边界。

让我们尝试分解这里发生的事情。

结构类型:

当您需要结构类型时,例如您在此方法中所做的:

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

Scala 所做的是使用反射来尝试找到 apply方法在运行时,并动态调用它。它被翻译成如下所示的东西:
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;
}

这是发出的反编译 Java 代码。长话短说,它寻找 apply方法。

Scala <= 2.11 会发生什么

对于 2.12 之前的任何 Scala 版本,声明匿名函数会导致编译器生成扩展 AbstractFunction* 的类。 , 其中 *是函数的arity。这些抽象函数类依次继承 Function* ,并实现他们的 apply方法与 lambda 的实现。

例如,如果我们采用您的表达方式:
val fun1 = (x:Int) => x * x 

编译器为我们发出:
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)
};
()

当我们查看字节码级别时,我们看到生成的匿名类是 apply方法:
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's happy.

瞧,Scala 2.12

在 Scala 2.12 中,我们得到了一种叫做 SAM 转换的东西。 SAM 类型是 Java 8 中的一项功能,它允许您缩写实现接口(interface),而是提供 lambda 表达式。例如:
new Thread(() -> System.out.println("Yay in lambda!")).start();

不必执行 Runnable并覆盖 public void run .
Scala 2.12 设定了一个目标,即尽可能通过 SAM 转换与 SAM 类型兼容。

在我们的特殊情况下,SAM 转换是可能的,这意味着代替 Scala 的 Function1[Int, Int]我们得到了 scala.runtime.java8.JFunction1$mcII$sp 的专用版本.这个 JFunction兼容Java,结构如下:
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))); }
}

这个 JFunction1已被专门化(就像我们在 Scala 中使用 @specialized 注解)为 def apply(i: Int): Int 发出一个特殊方法.请注意这里的一个重要因素,即此方法仅实现 apply Object => Object 形式的方法,而不是 Int => Int .现在我们可以开始了解为什么这可能会出现问题。

现在,当我们在 Scala 2.12 中编译相同的示例时,我们会看到:
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))
};
()

我们不再看到扩展 AbstractFunction* 的方法,我们只看到名为 $anonfun$main 的方法.当我们查看生成的字节码时,我们在内部看到它会调用 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

然而,如果我们明确扩展 Function1我们自己并实现 apply ,我们得到了与以前的 Scala 版本类似的行为,但不完全相同:
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()
};
{
()
}
}

我们不再扩展 AbstractFunction* ,但我们确实有 apply在运行时满足结构类型条件的方法。在字节码级别,我们看到 int apply(int) , 一个 object apply(object)以及注释 Function* 的 @specialization 属性的一堆案例:
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

结论:

我们可以看到 发生了变化实现细节 Scala 编译器在某些情况下如何处理 lambda 表达式。这是一个错误吗?我的感觉倾向于否定。 Scala 规范在任何地方都没有保证需要一个名为 apply 的方法。它与 lambda 的签名相匹配,这就是我们称之为实现细节的原因。虽然这确实是一个有趣的怪癖,但我不会在任何类型的生产环境中依赖这样的代码,因为它可能会发生变化。

关于scala - lambda 函数不也是具有 Function1 特征的对象吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41215697/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com