EffScript: Practical Effects for Scala
Customizable Gradual Polymorphic Effects for Scala accepted at OOPSLA 2015
EffScript is a small domain-specific language for writing tailored effect disciplines for Scala. In addition to being customizable, the underlying effect system supports both effect polymorphism (as developed by Lukas Rytz in his PhD thesis) and gradual effect checking (following the theory of Bañados, Garcia and Tanter).
Scala Implementation
The implementation of the Practical Effect system is developed as a compiler plugin for the Scala programming language. The plugin is based on the plugin developed by Rytz et al and is composed of two sub plugins to implement bidirectional checking: the effect inference plugin, and the effect checking plugin. The effect inference plugin is a modification of the one developed by Rytz et al, extended with support for gradual effects and the customizable effect system. In relation to bidirectional checking, Scala has inference of effect, therefore there are cases where there is absence of effect annotations. The effect inference plugin is necessary to annotate function abstractions that do not have effect annotations. This information about effect inference is used by the effect checking plugin to check and adjust sets of effect privileges, also inserting runtime checks of effect wherever it may be necessary.
Getting Started Guide
The implementation
The code and examples can be downloaded here.
Alternatively, a virtual box image can be downloaded here.
Access with username: ubuntu and password: ubuntu, all the code and examples can be found inside folder effscript_implementation
.
To run the examples, you need to have installed Scala (http://www.scala-lang.org/), SBT (http://www.scala-sbt.org/) and JDK1.7 (http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html).
In case we have Java 1.8 already installed, we need to change the JAVA_HOME
variable so it use Java 1.7 instead. For example:
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/HOME PATH=$JAVA_HOME/bin:$PATH sbt
Compiling and packaging the inference and checking library
For the effect inference library:
cd efftp sbt package
For the effect checking library:
cd gpes sbt package
How to use the compiler plugins
Let us create new project that will use the effect system with a custom “simpleIO” discipline (see eff-examples/simpleUI.eff
file). Note that we also provide a ready to use project in folder playground-src
:
name: simpleIO privileges: @simpleNoIO @simpleOutput @simpleInput lattice: top: @simpleOutput @simpleInput bottom: @simpleNoIO effspecs: def views.html.dummy.apply() prod @simpleNoIO def views.html.foo.apply[T]() prod @simpleNoIO call scala.Predef.read*: T prod @simpleInput call fakePrint(T <: String) prod @simpleOutput call readM prod @simpleInput def scala.Predef.read*(V): T prod @simpleInput
We first create the folder for our new project which we call playground
mkdir playground
We need to link the compiler libraries to our new project by creating symbolic links of the jars inside the new project. Note that we could have also copied the libraries but modifications to the discipline implies performing a new copy after each compilation. To do this step we have two options:
- Using the
copyCompilerLibs.sh
script\\At the root of the new project:chmod +x copyCompilerLibs.sh #make sure the file has execution permissions ./copyCompilerLibs.sh playground
ThecopyCompilerLibs.sh
script creates two symbolic links to a folder lib at the root of the provided path. - Creating the symbolic links manually
Alternatively we can create the symbolic links manually. ThecopyCompilerLibs.sh
script is equivalent to (note that the path to the jar must be absolute path, using a relative path will not work properly):cd playground mkdir -p lib ln -s [Absolute-path]/efftp/target/scala-2.10/effects-plugin_2.10-0.1-SNAPSHOT.jar lib/infer.jar ln -s [Absolute-path]/gpes/target/scala-2.10/effects-checker-plugin_2.10-0.1-SNAPSHOT.jar lib/check.jar
Next we need to edit a build.sbt
file to declare the project name, Scala version, libraries and compiler options.
The effect plugin works with Scala 2.10.3, which can be specified in every project by editing a build.sbt
file at the root of the project.
Our build.sbt
file looks like this:
name := "playground" version := "0.1" autoCompilerPlugins := true scalaVersion := "2.10.4" libraryDependencies ++= List( "org.scala-lang" % "scala-compiler" % "2.10.4" ) scalacOptions += "-Xplugin:lib/infer.jar" scalacOptions += "-Xplugin:lib/check.jar" scalacOptions += "-P:effects:domains:simpleIO" scalacOptions += "-P:effects:unchecks:java.*:scala.(?!Function)*"
Next, let us create a file helloGE.scala
to play with the effect system inside the folder “src/main/scala/”
Next, make sure helloGE.scala
looks like this
import scala.annotation.effects._ object HelloGradualEffects{ def main(args: Array[String]): Unit @simpleOutput = { println("hello gradual effects") } }
Were the body of main is only allowed to produce @simpleOutput
. We can test this by calling method “readInt” which produces @simpleInput
according to the effscript file:
def main(args: Array[String]): Unit @simpleOutput = { println("hello gradual effects") readInt() }
If we run the last code using sbt:
sbt >compile
Sbt will try to compile it, but it will raise a static error:
[error] found : @simpleInput [error] required: @simpleOutput [error] readInt() [error] ^ [error] one error found
Now let us test gradual effects:
def main(args: Array[String]): Unit @simpleOutput @pure= { def foo: Int @unknown = { readInt() } println("hello gradual effects") foo }
Running the code gives a runtime error:
>run ... [error] (run-main-1) runtimePrivileges.RuntimePrivileges$EffectPrivilegeException: Not enough privileges:simpleInput() [error] at scala.this.Predef.readInt() (helloGE.scala:16) runtimePrivileges.RuntimePrivileges$EffectPrivilegeException: Not enough privileges:simpleInput() ... Caused by: runtimePrivileges.RuntimePrivileges$EffectPrivilegeException: The localized restriction to "simpleOutput()" did not set all the required privileges at foo (helloGE.scala:20) The outermost restriction to "simpleOutput()" did not set the required privileges at { def foo: Int @scala.annotation.effects.simpleNoIO @scala.annotation.effects.unknown = { scal... (helloGE.scala:13) ...
Step by Step Instructions
Architectural Constraints in Play
The effscript File eff-examples/DBAccess.eff
enforces the MVC pattern in a Play framework application.
To modify the discipline, just edit the DBAccess.eff
file, and then run
cd effscript ./updateDBAccessDomain
The files to test the effect discipline are found in a play application located in folder play-slick-advanced-effects
. To run the application just run:
./copyCompilerLibs.sh play-slick-advanced-effects cd play-slick-advanced-effects ./activator run
And just access http://localhost:9000 to test the effect discipline.
The controller can be found in app/controllers/EffectController.scala
.
The models can be found in app/models/
The template can be found in app/views/index.scala.html
To test the access to the database from the templates, just uncomment line 3 of file app/views/index.scala.html
like this:
@{models.Users.byId(1) : @scala.annotation.effects.unknown}
And then reload the page:
To test that the EffectControllers only have access to insert User
s just uncomment line 19 of file app/controllers/EffectController.scala
:
Cars.insert(car)
And then reload the page to see how an static error is raised:
Modifying a discipline
Let us modify the simpleIO discipline defined in the previous section. Let us remember that the file can be found in eff-examples/simpleIO.eff
.
To modify the discipline, just edit the simpleIO.eff
file, and then run
cd effscript ./updateSimpleIODomain
Let us look at updateSimpleIODomain
:
First it runs a script that uses effscript to generate the classes needed for the compiler plugins:
sbt "run ../eff-examples/simpleIO.eff"
then it copies the generated files into each of the compiler plugins, overriding the existing implementations with the new ones:
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) cp target/SimpleIO.scala $DIR/../gpes/src/main/scala/annotation/effects/SimpleIO.scala cp target/SimpleIO.scala $DIR/../efftp/src/main/scala/scala/annotation/effects/SimpleIO.scala cp target/SimpleIODomain\(check\).scala $DIR/../gpes/src/main/scala/simpleIO/SimpleIODomain.scala cp target/SimpleIODomain\(infer\).scala $DIR/../efftp/src/main/scala/scala/tools/nsc/effects/simpleIO/SimpleIODomain.scala
After that step, we need to re-package both compiler plugins updating the .jar
files.
Creating a new discipline
To create a new discipline we need to do extra steps. Let us suppose we want to add a new discipline called “newDisc”. Once we have created our newDisc.eff
file we need to create the folders for this discipline in every compiler plugin project:
mkdir gpes/src/main/scala/newDisc mkdir efftp/src/main/scala/scala/tools/nsc/effects/newDisc
Then create and run a batch just like the previous step to generate the files and copy them into each compiler plugin project.
Next, we need to add this discipline into each of the compiler plugin projects:
For “gpes” we need to edit src/main/scala/EffectsCheckerPlugin.scala
file, and add a new case inside mkDomains
function:
... case "simpleTPIO" :: xs => new simpletpio.SimpleTPIODomain { val global: EffectChecker.this.global.type = EffectChecker.this.global override val uncheckedFunctions = settings.unchecks } :: mkDomains(xs) case "newDisc" :: xs => new newdisc.NewDiscDomain { val global: EffectChecker.this.global.type = EffectChecker.this.global override val uncheckedFunctions = settings.unchecks } :: mkDomains(xs) case x :: xs => mkDomains(xs)
For “efftp” we need to edit src/main/scala/tools/nsc/effects/EffectsPlugin.scala
file, and add a new case inside mkDomains
function:
... case "simpleTPIO" :: xs => new simpletpio.SimpleTPIODomain { val global: EffectsPlugin.this.global.type = EffectsPlugin.this.global override val uncheckedFunctions = efftpSettings.unchecks } :: mkDomains(xs) case "newDisc" :: xs => new newdisc.NewDiscDomain { val global: EffectsPlugin.this.global.type = EffectsPlugin.this.global override val uncheckedFunctions = efftpSettings.unchecks } :: mkDomains(xs) case x :: xs => global.abort(s"Unknown effect domain: $x")
Next, just re package each of the compiler plugin projects and update libraries of every project accordingly (unless symbolic links are being used).
Running Benchmarks
Benchmarks are inside benchmarks
folder.
Using Scala 2.10.x
For benchmarks, it is better to run them not using sbt as it is faster. To run the benchmarks using the scala
command you need to have the Scala version 2.10.x. For example, to run a benchmark using the scala command we can use the runExperiment
script file:
#!/bin/bash /usr/local/Cellar/scala210/2.10.4/bin/scala -cp CollsSimple/lib/check.jar:/usr/local/Cellar/scala210/2.10.4/libexec/lib/scala-reflect.jar $1 $2
Notice how the scala command and the reflection library correspond to version 2.10.4. We can run a “benchmark run” like this (edit this file to indicate the appropriate path for Scala binaries and libraries):
./runExperiment [jar] [version]
Where [version] can be:
- 0 ⇒ 95 dynamic checks and 67 context adjustements
- 1 ⇒ 35 dynamic checks and 67 context adjustements
- 2 ⇒ 35 dynamic checks and 67 context adjustements
- 3 ⇒ fully annotated (do not produce runtime effect checks)
The [jar] can be:
CollsNoeffs/target/scala-2.10/collsnoeffs_2.10-0.1.jar
⇒ without the effect systemCollsSimple/target/scala-2.10/collssimple_2.10-0.1.jar
⇒ default effect system.CollsSimpleBits/target/scala-2.10/collssimple_2.10-0.1.jar
⇒ default effect system using bit vectors.CollsSimpleSE/target/scala-2.10/collssimplese_2.10-0.1.jar
⇒ scenario with subeffecting.CollsSimpleTP/target/scala-2.10/collssimpletp_2.10-0.1.jar
⇒ scenario with subeffecting and type parameters.
For example, to run the default effect system with the version with most dynamic checks:
./runExperiment CollsSimple/target/scala-2.10/collssimple_2.10-0.1.jar 0
The program returns the number of second of the benchmark.
Using SBT
In case we do not have Scala 2.10 we can always run each benchmark by entering to the folder of the experiment. The folders are the following:
CollsNoeffs
⇒ without the effect systemCollsSimple
⇒ default effect system.CollsSimpleBits
⇒ default effect system using bit vectors.CollsSimpleSE
⇒ scenario with subeffecting.CollsSimpleTP
⇒ scenario with subeffecting and type parameters.
For instance:
cd CollsSimple
The versions of each experiment are the following:
- 0 ⇒ 95 dynamic checks and 67 context adjustements
- 1 ⇒ 35 dynamic checks and 67 context adjustements
- 2 ⇒ 35 dynamic checks and 67 context adjustements
- 3 ⇒ fully annotated (do not produce runtime effect checks)
For instance, to run the version 2 of the experiment, we run:
sbt "run 2"
Plotting the results
To plot the benchmark results we have provided a zip file with the required files plot.zip.
You will need the following python libraries (I recommend install them using “easy_install” command of setuptools https://pypi.python.org/pypi/setuptools): - numpy - matplotlib
Place the content of the zip file inside the benchmark folder. Then, edit runbenchmark
file to set the number of iteration by editing variable n
:
#!/bin/bash n=1 ...
Before running the benchmarks we recommend re packaging the CollsSimple project. The reason for this is that at the moment of the artifact submission, CollsSimple was compiled and packaged using the bit vector version of the compiler plugins. Later we updated the effect compiler plugins but we did not repackage the project.
cd CollsSimple
sbt package
To run the benchmarks, at the root of the benchmarks folder run:
./runbenchmark > outputbenchmark
The execution will output the results in outputbenchmark
file. We have provided a outputbenchmark
example file.
To plot the results just run:
python buildGraph.py
It will generate a benchmark.pdf
file with the plot. We have provided a benchmark.pdf
example file.