从Scala REPL中查看<function1>的内容

16

我可以在Scala REPL中创建匿名函数,方法如下:

scala> val a = (x: Int) => x * x
a: Int => Int = <function1>

但是有没有办法在创建后看到 <div> 内部的内容呢?

我在思考一些情况,比如我会取一个函数并返回一个函数。我只是好奇地想看看repl作为返回值创建的内容,而不仅仅是返回值的类型,所以类似这样的东西:

scala> val b = (f: (Int => Boolean)) => (x: Int) => ! (f(x))
b: (Int => Boolean) => (Int => Boolean) = <function1>

scala> val c = b((x: Int) => x % 2 == 0)
c: Int => Boolean = <function1>

我想看看 c 里面生成的代码!

5个回答

11

这并不像使用Clojure实现相同功能那样方便,因为它显示的是编译后的代码,但你可以利用:javap来获得优势:

scala> :javap -help
usage       :javap [opts] [path or class or -]...
-help       Prints this help message
-raw        Don't unmangle REPL names
-app        Show the DelayedInit body of Apps
-fun        Show anonfuns for class or Class#method
-verbose/-v Stack size, number of locals, method args
-private/-p Private classes and members
-package    Package-private classes and members
-protected  Protected classes and members
-public     Public classes and members
-l          Line and local variable tables
-c          Disassembled code
-s          Internal type signatures
-sysinfo    System info of class
-constants  Static final constants

scala> :javap -s a
Compiled from "<console>"
public class  {
  public static final  MODULE$;
    descriptor: L;
  public static {};
    descriptor: ()V

  public scala.Function1<java.lang.Object, java.lang.Object> a();
    descriptor: ()Lscala/Function1;

  public ();
    descriptor: ()V
}

或者使用scala -Xprint:typer指示REPL在编译时输出代码内部(尽管可能过于冗长,也许有人可以建议使用更简洁的编译器阶段)。

顺便说一下,在:javap输出中可以看到,每个REPL表达式都隐式地包含周围的代码,不要被混淆了 - scala通常不执行它。


好的,这是一个开始!我会保持问题开放,看看是否还有其他人有见解,但我怀疑这是我们在repl上能得到的最好的结果!谢谢 :) - Kat
似乎没有其他人知道!感谢您的回答! - Kat
@Kat:如果我正确理解了你的问题,那么javap在这里真的没有帮助,因为b返回的每个函数都有相同的代码。 返回的函数之间的区别仅在于它们关闭了不同的f值。(你的问题,特别是你对“生成”一词的使用,让我想知道你是否正在设想一个不同的基础机制。) - Seth Tisue

6

这是在REPL之外完成的,但您可以使用scalac编译器并使用-print选项编译代码。运行scalac -help 命令会给出该选项的描述:

-print        Print program with Scala-specific features removed.

我用这个小程序试了一下:

object Test {
    def main(args: Array[String]): Unit = {
        val a = (x: Int) => x * x
    }
}

然后它给了我这个输出:

$ scalac -print Test.scala
[[syntax trees at end of                   cleanup]] // Test.scala
package <empty> {
  object Test extends Object {
    def main(args: Array[String]): Unit = {
      val a: Function1 = {
        (new <$anon: Function1>(): Function1)
      };
      ()
    };
    def <init>(): Test.type = {
      Test.super.<init>();
      ()
    }
  };
  @SerialVersionUID(value = 0) final <synthetic> class anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp with Serializable {
    final def apply(x: Int): Int = anonfun$1.this.apply$mcII$sp(x);
    <specialized> def apply$mcII$sp(x: Int): Int = x.*(x);
    final <bridge> <artifact> def apply(v1: Object): Object = scala.Int.box(anonfun$1.this.apply(scala.Int.unbox(v1)));
    def <init>(): <$anon: Function1> = {
      anonfun$1.super.<init>();
      ()
    }
  }
}

5
要查看函数字面量,请使用:javap -fun
$ scala
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_51).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def f = (1 to 10) map (_ * 2)
f: scala.collection.immutable.IndexedSeq[Int]

scala> :javap -fun f
Compiled from "<console>"
public final class $anonfun$f$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable {
  public static final long serialVersionUID;
  public final int apply(int);
  public int apply$mcII$sp(int);
  public final java.lang.Object apply(java.lang.Object);
  public $anonfun$f$1();
}

这是传递给map函数的匿名函数。

要过滤应用方法(即函数体),请使用尾随的#f#apply

scala> :javap -fun f#
  public final int apply(int);
  public int apply$mcII$sp(int);
  public final java.lang.Object apply(java.lang.Object);

那包括专业的方法。
scala> :javap -fun -prv f#
  public final int apply(int);
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: invokevirtual #21                 // Method apply$mcII$sp:(I)I
         5: ireturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   L$line3/$read$$iw$$iw$$anonfun$f$1;
               0       6     1   x$1   I
      LineNumberTable:
        line 10: 0
  public int apply$mcII$sp(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1       
         1: iconst_2      
         2: imul          
         3: ireturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       4     0  this   L$line3/$read$$iw$$iw$$anonfun$f$1;
               0       4     1   x$1   I
      LineNumberTable:
        line 10: 0
  public final java.lang.Object apply(java.lang.Object);
    flags: ACC_PUBLIC, ACC_FINAL, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: aload_1       
         2: invokestatic  #32                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
         5: invokevirtual #34                 // Method apply:(I)I
         8: invokestatic  #38                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
        11: areturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      12     0  this   L$line3/$read$$iw$$iw$$anonfun$f$1;
               0      12     1    v1   Ljava/lang/Object;
      LineNumberTable:
        line 10: 0

scala> 

对于vals,首先查看评估的构造函数:

scala> :javap -fun c
Failed: No closures found.

scala> :javap -prv c
[snip]
  public $line7.$read$$iw$$iw$();
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #20                 // Method java/lang/Object."<init>":()V
         4: aload_0       
         5: putstatic     #22                 // Field MODULE$:L$line7/$read$$iw$$iw$;
         8: aload_0       
         9: getstatic     #27                 // Field $line6/$read$$iw$$iw$.MODULE$:L$line6/$read$$iw$$iw$;
        12: invokevirtual #30                 // Method $line6/$read$$iw$$iw$.b:()Lscala/Function1;
        15: new           #32                 // class $line7/$read$$iw$$iw$$anonfun$1
        18: dup           
        19: invokespecial #33                 // Method $line7/$read$$iw$$iw$$anonfun$1."<init>":()V
        22: invokeinterface #39,  2           // InterfaceMethod scala/Function1.apply:(Ljava/lang/Object;)Ljava/lang/Object;
[snip]

然后剪切/粘贴anonfun的名称,可选择添加#以仅筛选应用方法:

scala> :javap -prv $line7/$read$$iw$$iw$$anonfun$1#
  public final boolean apply(int);
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0       
         1: iload_1       
         2: invokevirtual #18                 // Method apply$mcZI$sp:(I)Z
         5: ireturn       
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       6     0  this   L$line7/$read$$iw$$iw$$anonfun$1;
               0       6     1     x   I
      LineNumberTable:
        line 12: 0
  public boolean apply$mcZI$sp(int);
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_1       
         1: iconst_2      
         2: irem          
         3: iconst_0      
         4: if_icmpne     11
         7: iconst_1      
         8: goto          12
        11: iconst_0      
        12: ireturn       
[snip]

存储在c中的函数:
scala> $intp.isettings.unwrapStrings = false
$intp.isettings.unwrapStrings: Boolean = false

scala> c.getClass
res4: Class[_ <: Int => Boolean] = class $line3.$read$$iw$$iw$$anonfun$1$$anonfun$apply$1

scala> :javap -prv $line3.$read$$iw$$iw$$anonfun$1$$anonfun$apply$1
[snip]
-raw选项用于:javap,可以显示REPL的包和封装对象。如果要在普通输出中看到它们,您需要关闭输出过滤,如下所示。
或者:
scala> $intp.withoutUnwrapping(println(c.getClass))
class $line3.$read$$iw$$iw$$anonfun$1$$anonfun$apply$1

通常匿名函数很简短,因此不需要过滤应用程序方法。
请注意,我已经将这个功能剥离出来,转换为Java 8 lambda后,因为编码一直在变化。也许有朝一日它会被恢复。

3

运行时没有任何东西可以很好地打印编译代码。

您可以编写一个宏来打印树的源代码并使用它吗? 大多数宏教程都以打印源代码的宏开头 - 例如,请参见http://www.warski.org/blog/2012/12/starting-with-scala-macros-a-short-tutorial/

也许:

// Given a partial function "pf", return the source code for pf
// as a string as well as the compiled, runnable function itself
def functionAndSource(pf: PartialFunction[Any, Any]): (String, PartialFunction[Any, Any]) = macro functionAndSourceImpl

def functionAndSourceImpl = ...

val pm1: (String, PartialFunction[Any, Any]) = functionAndSource {
  case "foo" => R1
}

在Scala中,这永远不会像Lisp或Ruby那样容易或好用。Scala是一种编译语言,它没有针对代码本身的反射优化。

(有关非常相似的问题,请参见Scala Pattern Matching pretty printed。)


3

尝试使用我的scala-to-java工具。它可以将给定的scala源代码编译,并使用Procyon反编译器将其反编译为java。

对于您的scala输入:

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

它显示了这个反编译输出:

import scala.*;
import scala.runtime.*;

public final class _$$anon$1$$anonfun$1 extends AbstractFunction1$mcII$sp implements Serializable {
    @Override
    public final int apply(final int x) {
        return this.apply$mcII$sp(x);
    }

    @Override
    public int apply$mcII$sp(final int x) {
        return x * x;
    }
}

import scala.*;

public final class _$$anon$1 {
    private final Function1<Object, Object> a = new _$$anon$1$$anonfun._$$anon$1$$anonfun$1(this);

    private Function1<Object, Object> a() {
        return (Function1<Object, Object>)this.a;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接