|
1 | | -## Functional Interfaces for (specialized) Scala functions |
| 1 | +## Functional Interfaces for Scala functions |
2 | 2 |
|
3 | | -An exploration of Java8 friendly encoding of Scala `FunctionN`. |
| 3 | +A set of [Functional Interfaces](http://download.java.net/jdk8/docs/api/java/lang/FunctionalInterface.html) |
| 4 | +for `scala.FunctionN`. These are designed for convenient construction of Scala functions |
| 5 | +using Java 8 lambda syntax. |
4 | 6 |
|
5 | | -There are two goals: |
6 | | - |
7 | | - - Encode anonymous functions in the same manner as Java 8 lambdas |
8 | | -(LambdaMetafactory) without losing the benefits of specialization. |
9 | | - - Enable Java code to treat `scala.Function1` as an functional interface |
10 | | - |
11 | | -## `Function1` via `LambdaMetafactory` |
12 | | - |
13 | | -Benefits: smaller bytecode, profit from ongoing JVM optimizations |
14 | | -for lambda elision, inlining, etc. |
15 | | - |
16 | | -This requires a functional interface for FunctionN, which is a bit |
17 | | -harder than it sounds in the face of specialized variants of apply, |
18 | | -as well as compose/andThen. But, without changing our library at all, |
19 | | -this still look possible! |
20 | | - |
21 | | -We need to create interfaces for all specialized variants of Function1. |
22 | | -The abstract method is the specialized apply, and all other applies must |
23 | | -forward to this. |
24 | | - |
25 | | -To emit smaller code, we can create a base functional interface in which |
26 | | -the generic apply is abstract, and all of the specialized variants forward |
27 | | -to it. This way, each specialized functional interface need only reabstract |
28 | | -one specialized apply and redirect the unspecialized apply to it. |
29 | | - |
30 | | -Here's how they could look: |
31 | | - |
32 | | - - `scala.Function1` |
33 | | - - [`runtime.F1`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/F1.java) |
34 | | - - [`runtime.F1$mcII$sps`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/F1%24mcII%24sp.java) |
35 | | - - ... (other specialized variants) |
36 | | - |
37 | | -We will then need to modify the backend of scalac to emit |
38 | | -`invokedynamic` against the `LambdaMetafactory`, passing a method |
39 | | -handle to the function-body-in-a-method that results from `-Ydelambdafy:method` |
40 | | -lifted method body. This behaviour would be conditional on a flag, and require |
41 | | -that you have `F1*` on the classpath at runtime. These could be shipped in a |
42 | | -separate JAR. |
43 | | - |
44 | | -We could actually do all of this without needing to emit any default methods ourselves; we can simply use a code generator and javac to generate `F1*`! |
45 | | - |
46 | | -### Optimizer |
47 | | - |
48 | | -We will need to modify `GenBCodeOpt`'s to understand `indy` calls to spin |
49 | | -up lambdas so it can still recognize opportunities for closure inlining. |
50 | | - |
51 | | -### Bridges |
52 | | - |
53 | | -Today, in Scala: |
54 | | - |
55 | | -``` |
56 | | -scala> ((s: String) => s).getClass.getDeclaredMethods.mkString("\n") |
57 | | -res2: String = |
58 | | -public final java.lang.String $anonfun$1.apply(java.lang.String) |
59 | | -public final java.lang.Object $anonfun$1.apply(java.lang.Object) |
60 | | -``` |
61 | | - |
62 | | -In Java8, the the metafactory just spins up a class with *just* the generic |
63 | | -signature. This is safe as the class is anonymous and only ever |
64 | | -called through invoke-interface, so no harm done. Seems like a leaner |
65 | | -representation. |
66 | | - |
67 | | -Furthermore, by *only* creating the generic signature for anonymous functions, |
68 | | -we would avoid the rather brutal limitation imposed by erasure for value classes, [SI-6260](https://issues.scala-lang.org/browse/SI-6260). |
69 | | - |
70 | | -LamdaMetaFactory does have an [advanced API](http://download.java.net/jdk8/docs/api/java/lang/invoke/LambdaMetafactory.html#FLAG_BRIDGES) |
71 | | -that allows to create additional bridges, if needed. We can also mark |
72 | | -the closure as serializable, which should be done to be compatible |
73 | | -with what we do today. |
74 | | - |
75 | | -## `scala.Function1` as a functional interface |
76 | | - |
77 | | -To do this, we would need to pull up the defender methods from s.r.F1 |
78 | | -directly to the trait interface class in the standard library. We can |
79 | | -definitely do this when we mandate Java 8. But is it safe to do it earlier? |
80 | | - |
81 | | -I notice that `Function.{andThen, compose}` are still generated in |
82 | | -specialized droves, despite the attempt to mark them as @unspecialized. |
83 | | -This looks like a bug in scalac. |
84 | | - |
85 | | -## `invokedynamic` calls, courtesy of javac |
86 | | - |
87 | | -[`Test.java`](https://github.com/retronym/java-8-function1/blob/master/src/main/java/scala/runtime/Test.java) contains Java 8 lambdas against `F1` and `F1$mcII$sp`. |
88 | | - |
89 | | -In the following decompilation, you can see the invokedynamic calls: |
90 | | - |
91 | | -``` |
92 | | -% `java_home -v 1.8`/bin/javap -v -p -classpath target/scala-2.11.0-M7/classes Test |
93 | | -Classfile /Users/jason/code/java-8-function1/target/scala-2.11.0-M7/classes/Test.class |
94 | | - Last modified Jan 28, 2014; size 1890 bytes |
95 | | - MD5 checksum 7b6665961e5a4a10440571d0fcbdd2b3 |
96 | | - Compiled from "Test.java" |
97 | | -public class Test |
98 | | - SourceFile: "Test.java" |
99 | | - InnerClasses: |
100 | | - static #2; //class Test$1 |
101 | | - public static final #88= #87 of #90; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles |
102 | | - BootstrapMethods: |
103 | | - 0: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; |
104 | | - Method arguments: |
105 | | - #50 (Ljava/lang/Object;)Ljava/lang/Object; |
106 | | - #51 invokestatic Test.lambda$main$0:(Ljava/lang/String;)Ljava/lang/String; |
107 | | - #52 (Ljava/lang/String;)Ljava/lang/String; |
108 | | - 1: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; |
109 | | - Method arguments: |
110 | | - #50 (Ljava/lang/Object;)Ljava/lang/Object; |
111 | | - #57 invokestatic Test.lambda$main$1:(Ljava/lang/Integer;)Ljava/lang/Integer; |
112 | | - #58 (Ljava/lang/Integer;)Ljava/lang/Integer; |
113 | | - 2: #49 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; |
114 | | - Method arguments: |
115 | | - #62 (I)I |
116 | | - #63 invokestatic Test.lambda$main$2:(I)I |
117 | | - #62 (I)I |
118 | | - minor version: 0 |
119 | | - major version: 52 |
120 | | - flags: ACC_PUBLIC, ACC_SUPER |
121 | | -Constant pool: |
122 | | - ... |
123 | | -{ |
124 | | - public Test(); |
125 | | - descriptor: ()V |
126 | | - flags: ACC_PUBLIC |
127 | | - Code: |
128 | | - stack=1, locals=1, args_size=1 |
129 | | - 0: aload_0 |
130 | | - 1: invokespecial #1 // Method java/lang/Object."<init>":()V |
131 | | - 4: return |
132 | | - LineNumberTable: |
133 | | - line 1: 0 |
134 | | - LocalVariableTable: |
135 | | - Start Length Slot Name Signature |
136 | | - 0 5 0 this LTest; |
137 | | -
|
138 | | - public static void main(java.lang.String[]); |
139 | | - descriptor: ([Ljava/lang/String;)V |
140 | | - flags: ACC_PUBLIC, ACC_STATIC |
141 | | - Code: |
142 | | - stack=2, locals=4, args_size=1 |
143 | | - 0: new #2 // class Test$1 |
144 | | - 3: dup |
145 | | - 4: invokespecial #3 // Method Test$1."<init>":()V |
146 | | - 7: pop |
147 | | - 8: invokedynamic #4, 0 // InvokeDynamic #0:apply:()Lscala/runtime/F1; |
148 | | - 13: astore_1 |
149 | | - 14: aload_1 |
150 | | - 15: ldc #5 // String |
151 | | - 17: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object; |
152 | | - 22: pop |
153 | | - 23: invokedynamic #7, 0 // InvokeDynamic #1:apply:()Lscala/runtime/F1; |
154 | | - 28: astore_2 |
155 | | - 29: aload_2 |
156 | | - 30: iconst_0 |
157 | | - 31: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; |
158 | | - 34: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object; |
159 | | - 39: pop |
160 | | - 40: aload_2 |
161 | | - 41: iconst_0 |
162 | | - 42: invokeinterface #9, 2 // InterfaceMethod scala/runtime/F1.apply$mcII$sp:(I)I |
163 | | - 47: pop |
164 | | - 48: invokedynamic #10, 0 // InvokeDynamic #2:apply$mcII$sp:()Lscala/runtime/F1$mcII$sp; |
165 | | - 53: astore_3 |
166 | | - 54: aload_2 |
167 | | - 55: iconst_1 |
168 | | - 56: invokeinterface #9, 2 // InterfaceMethod scala/runtime/F1.apply$mcII$sp:(I)I |
169 | | - 61: pop |
170 | | - 62: aload_2 |
171 | | - 63: iconst_1 |
172 | | - 64: invokestatic #8 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; |
173 | | - 67: invokeinterface #6, 2 // InterfaceMethod scala/runtime/F1.apply:(Ljava/lang/Object;)Ljava/lang/Object; |
174 | | - 72: pop |
175 | | - 73: return |
176 | | - LineNumberTable: |
177 | | - line 3: 0 |
178 | | - line 6: 8 |
179 | | - line 8: 14 |
180 | | - line 10: 23 |
181 | | - line 11: 29 |
182 | | - line 12: 40 |
183 | | - line 14: 48 |
184 | | - line 16: 54 |
185 | | - line 17: 62 |
186 | | - line 18: 73 |
187 | | - LocalVariableTable: |
188 | | - Start Length Slot Name Signature |
189 | | - 0 74 0 args [Ljava/lang/String; |
190 | | - 14 60 1 f1 Lscala/runtime/F1; |
191 | | - 29 45 2 f2 Lscala/runtime/F1; |
192 | | - 54 20 3 f3 Lscala/runtime/F1$mcII$sp; |
193 | | - LocalVariableTypeTable: |
194 | | - Start Length Slot Name Signature |
195 | | - 14 60 1 f1 Lscala/runtime/F1<Ljava/lang/String;Ljava/lang/String;>; |
196 | | - 29 45 2 f2 Lscala/runtime/F1<Ljava/lang/Integer;Ljava/lang/Integer;>; |
197 | | -
|
198 | | - private static int lambda$main$2(int); |
199 | | - descriptor: (I)I |
200 | | - flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC |
201 | | - Code: |
202 | | - stack=1, locals=1, args_size=1 |
203 | | - 0: iload_0 |
204 | | - 1: ireturn |
205 | | - LineNumberTable: |
206 | | - line 14: 0 |
207 | | - LocalVariableTable: |
208 | | - Start Length Slot Name Signature |
209 | | - 0 2 0 i I |
210 | | -
|
211 | | - private static java.lang.Integer lambda$main$1(java.lang.Integer); |
212 | | - descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer; |
213 | | - flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC |
214 | | - Code: |
215 | | - stack=1, locals=1, args_size=1 |
216 | | - 0: aload_0 |
217 | | - 1: areturn |
218 | | - LineNumberTable: |
219 | | - line 10: 0 |
220 | | - LocalVariableTable: |
221 | | - Start Length Slot Name Signature |
222 | | - 0 2 0 i Ljava/lang/Integer; |
223 | | -
|
224 | | - private static java.lang.String lambda$main$0(java.lang.String); |
225 | | - descriptor: (Ljava/lang/String;)Ljava/lang/String; |
226 | | - flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC |
227 | | - Code: |
228 | | - stack=1, locals=1, args_size=1 |
229 | | - 0: aload_0 |
230 | | - 1: areturn |
231 | | - LineNumberTable: |
232 | | - line 6: 0 |
233 | | - LocalVariableTable: |
234 | | - Start Length Slot Name Signature |
235 | | - 0 2 0 s Ljava/lang/String; |
236 | | -} |
237 | | -``` |
0 commit comments