Skip to content

Function Converters #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,69 @@ class Test {

[More Examples / Documentation](src/test/java/scala/compat/java8/LambdaTest.java)

## Converters between `scala.FunctionN` and `java.util.function`

A set of converters that enable interconversion between Java's standard
Functional Interfaces defined in `java.util.function` and Scala's `Function0`,
`Function1`, and `Function2` traits. These are intended for use when you
already have an instance of a `java.util.function` and need a Scala function,
or have a Scala function and need an instance of a `java.util.function`.

The `.asScala` extension method will convert a `java.util.function` to the corresponding
Scala function. The `.asJava` extension method will convert a Scala function to
the most specific corresponding Java functional interface. If you wish to obtain
a less specific functional interface, there are named methods that start with `asJava`
and continue with the name of the Java functional interface. For instance, the
most specific interface corresponding to the Scala function `val rev = (s: String) => s.reverse`
is `UnaryOperator[String]`, and that is what `rev.asJava` will produce. However,
`asJavaFunction(rev)` will return a `java.util.function.Function[String, String]` instead.

The `asJava` methods can also be called conveniently from Java. There are additional
`asScalaFrom` methods (e.g. `asScalaFromUnaryOperator`) that will perform the
functional-interface-to-Scala-function conversion; this is primarily of use when calling
from Java since the `.asScala` extension method is more convenient in Scala.

#### Usage examples

In Scala:

```scala
import java.util.function._
import scala.compat.java8.FunctionConverters._

val foo: Int => Boolean = i => i > 7
def testBig(ip: IntPredicate) = ip.test(9)
println(testBig(foo.asJava)) // Prints true

val bar = new UnaryOperator[String]{ def apply(s: String) = s.reverse }
List("cod", "herring").map(bar.asScala) // List("doc", "gnirrih")

def testA[A](p: Predicate[A])(a: A) = p.test(a)
println(testA(asJavaPredicate(foo))(4)) // Prints false

// println(testA(foo.asJava)(4)) <-- doesn't work
// IntPredicate does not extend Predicate!
```

In Java:

```java
import java.util.function.*;
import scala.compat.java8.FunctionConverters;

class Example {
String foo(UnaryOperator<String> f) {
return f.apply("halibut");
}
String bar(scala.Function1<String, String> f) {
return foo(functionConverters.asJavaUnaryOperator(f));
}
String baz(Function<String, String> f) {
return bar(functionConverters.asScalaFromFunction(f));
}
}
```

## Converters between `scala.concurrent` and `java.util.concurrent`

- [API](src/main/scala/scala/compat/java8/FutureConverters.scala)
Expand Down Expand Up @@ -53,6 +116,7 @@ class Test {
}
```


## Future work
- Converters for `java.util.function`, `java.util.stream`
- Converters for `java.util.stream`
- [`Spliterator`](https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html)s for Scala collections
191 changes: 107 additions & 84 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,91 +1,114 @@
scalaModuleSettings

scalaVersion := "2.11.6"

organization := "org.scala-lang.modules"

name := "scala-java8-compat"

version := "0.6.0-SNAPSHOT"

// important!! must come here (why?)
scalaModuleOsgiSettings

OsgiKeys.exportPackage := Seq(s"scala.compat.java8.*;version=${version.value}")

OsgiKeys.privatePackage := List("scala.concurrent.java8.*")

libraryDependencies += "junit" % "junit" % "4.11" % "test"

libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.4" % "test"

libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test"

mimaPreviousVersion := None

testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a")

sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
def write(name: String, content: String) = {
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
IO.write(f, content)
f
}
(
Seq(write("JFunction", CodeGen.factory)) ++
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
CodeGen.specializedF0.map((write _).tupled) ++
CodeGen.specializedF1.map((write _).tupled) ++
CodeGen.specializedF2.map((write _).tupled)
)
}

sourceGenerators in Test <+= sourceManaged in Test map { dir =>
def write(name: String, content: String) = {
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
IO.write(f, content)
f
}
Seq(write("TestApi", CodeGen.testApi))
}

initialize := {
// Run previously configured inialization...
initialize.value
// ... and then check the Java version.
val specVersion = sys.props("java.specification.version")
if (Set("1.5", "1.6", "1.7") contains specVersion)
sys.error("Java 8 or higher is required for this project.")
}

val disableDocs = sys.props("nodocs") == "true"

publishArtifact in packageDoc := !disableDocs

lazy val JavaDoc = config("genjavadoc") extend Compile

sources in (Compile, doc) := {
val orig = (sources in (Compile, doc)).value
orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449
def jwrite(dir: java.io.File)(name: String, content: String) = {
val f = dir / "scala" / "compat" / "java8" / s"${name}.java"
IO.write(f, content)
f
}

inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq(
packageDoc in Compile <<= packageDoc in JavaDoc,
sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) =>
val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java"))
allJavaSources.filterNot(_.getName.contains("FuturesConvertersImpl.java")) // this file triggers bugs in genjavadoc
},
javacOptions in JavaDoc := Seq(),
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
))

initialCommands :=
"""|import scala.concurrent._
|import ExecutionContext.Implicits.global
|import java.util.concurrent.{CompletionStage,CompletableFuture}
|import scala.compat.java8.FutureConverter._
|""".stripMargin
lazy val commonSettings = Seq(
scalaVersion := "2.11.6",
organization := "org.scala-lang.modules",
version := "0.6.0-SNAPSHOT",
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value,
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
)

lazy val fnGen = (project in file("fnGen")).
settings(commonSettings: _*).
settings(
fork in run := true // Needed if you run this project directly
)

lazy val root = (project in file(".")).
dependsOn(fnGen).
settings(scalaModuleSettings: _*).
settings(commonSettings: _*).
settings(
name := "scala-java8-compat"
).
settings(
// important!! must come here (why?)
scalaModuleOsgiSettings: _*
).
settings(
fork := true, // This must be set so that runner task is forked when it runs fnGen and the compiler gets a proper classpath

OsgiKeys.exportPackage := Seq(s"scala.compat.java8.*;version=${version.value}"),

OsgiKeys.privatePackage := List("scala.concurrent.java8.*"),

libraryDependencies += "junit" % "junit" % "4.11" % "test",

libraryDependencies += "org.apache.commons" % "commons-lang3" % "3.4" % "test",

libraryDependencies += "com.novocode" % "junit-interface" % "0.10" % "test",

mimaPreviousVersion := None,

testOptions += Tests.Argument(TestFrameworks.JUnit, "-v", "-a"),

(sourceGenerators in Compile) += Def.task {
val out = (sourceManaged in Compile).value
if (!out.exists) IO.createDirectory(out)
val canon = out.getCanonicalPath
val args = (new File(canon, "FunctionConverters.scala")).toString :: Nil
val runTarget = (mainClass in Compile in fnGen).value getOrElse "No main class defined for function conversion generator"
val classPath = (fullClasspath in Compile in fnGen).value
toError(runner.value.run(runTarget, classPath.files, args, streams.value.log))
(out ** "*.scala").get
}.taskValue,

sourceGenerators in Compile <+= sourceManaged in Compile map { dir =>
val write = jwrite(dir) _
Seq(write("JFunction", CodeGen.factory)) ++
(0 to 22).map(n => write("JFunction" + n, CodeGen.fN(n))) ++
(0 to 22).map(n => write("JProcedure" + n, CodeGen.pN(n))) ++
CodeGen.specializedF0.map(write.tupled) ++
CodeGen.specializedF1.map(write.tupled) ++
CodeGen.specializedF2.map(write.tupled)
},

sourceGenerators in Test <+= sourceManaged in Test map { dir =>
Seq(jwrite(dir)("TestApi", CodeGen.testApi))
},

initialize := {
// Run previously configured inialization...
initialize.value
// ... and then check the Java version.
val specVersion = sys.props("java.specification.version")
if (Set("1.5", "1.6", "1.7") contains specVersion)
sys.error("Java 8 or higher is required for this project.")
},

publishArtifact in packageDoc := !disableDocs,

sources in (Compile, doc) := {
val orig = (sources in (Compile, doc)).value
orig.filterNot(_.getName.endsWith(".java")) // raw types not cooked by scaladoc: https://issues.scala-lang.org/browse/SI-8449
}
).
settings(
(inConfig(JavaDoc)(Defaults.configSettings) ++ (if (disableDocs) Nil else Seq(
packageDoc in Compile <<= packageDoc in JavaDoc,
sources in JavaDoc <<= (target, compile in Compile, sources in Compile) map {(t, c, s) =>
val allJavaSources = (t / "java" ** "*.java").get ++ s.filter(_.getName.endsWith(".java"))
allJavaSources.filterNot(_.getName.contains("FuturesConvertersImpl.java")) // this file triggers bugs in genjavadoc
},
javacOptions in JavaDoc := Seq(),
artifactName in packageDoc in JavaDoc := ((sv, mod, art) => "" + mod.name + "_" + sv.binary + "-" + mod.revision + "-javadoc.jar"),
libraryDependencies += compilerPlugin("com.typesafe.genjavadoc" % "genjavadoc-plugin" % "0.8" cross CrossVersion.full),
scalacOptions in Compile <+= target map (t => "-P:genjavadoc:out=" + (t / "java"))
))): _*
).
settings(
initialCommands :=
"""|import scala.concurrent._
|import ExecutionContext.Implicits.global
|import java.util.concurrent.{CompletionStage,CompletableFuture}
|import scala.compat.java8.FutureConverter._
|""".stripMargin
)
Loading