Skip to content

Commit 91710fe

Browse files
committed
Code generator for functional interfaces for scala.Function0-22
- Update to Scala 2.11.0-RC1 (which doesn't have specialized variants for `compose/andThen`) - Generate: - `F0-22` which used default methods to: - delegate to `compose`, `andThen`, `curried` and `tupled` - implement the specialized variants of `apply`. - `P1-22` which for Unit returning functions (the functional interface returns `Object` and discards) - `F.{pN, fN}`, which are factory methods to minimize the type annotations needed in Java sources when adapting a lambda to `scala.FunctionN`.
1 parent 017ff20 commit 91710fe

File tree

7 files changed

+86
-440
lines changed

7 files changed

+86
-440
lines changed

LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Copyright (C) 2012-2014 EPFL
2+
Copyright (C) 2012-2014 Typesafe, Inc.
3+
4+
All rights reserved.
5+
6+
Redistribution and use in source and binary forms, with or without modification,
7+
are permitted provided that the following conditions are met:
8+
9+
* Redistributions of source code must retain the above copyright notice,
10+
this list of conditions and the following disclaimer.
11+
* Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
* Neither the name of the EPFL nor the names of its contributors
15+
may be used to endorse or promote products derived from this software
16+
without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 4 additions & 235 deletions
Original file line numberDiff line numberDiff line change
@@ -1,237 +1,6 @@
1-
## Functional Interfaces for (specialized) Scala functions
1+
## Functional Interfaces for Scala functions
22

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.
46

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-
```

build.sbt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1-
scalaVersion := "2.11.0-M7"
1+
scalaVersion := "2.11.0-RC1"
22

3-
javaHome := Some(file("/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home"))
3+
javaHome := Some(file("/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home"))
4+
5+
sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
6+
def write(name: String, content: String) = {
7+
val f = dir / "scala" / "runtime" / s"${name}.java"
8+
IO.write(f, content)
9+
f
10+
}
11+
Seq(write("F", CodeGen.factory)) ++ (0 to 22).map(n => write("F" + n, CodeGen.fN(n))) ++ (1 to 22).map(n => write("P" + n, CodeGen.pN(n)))
12+
}

src/main/java/scala/runtime/F1$mcII$sp.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)