This is an old revision of the document!


EffScript: Practical Effects for Scala

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. As explained before, 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.

The code and examples can be downloaded here. To run the examples, you need to have installed Scala (http://www.scala-lang.org/) and SBT (http://www.scala-sbt.org/).

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 examples/simpleUI.eff file):

name: simpleIO

privileges:
        @simpleNoIO
        @simpleOutput
        @simpleInput

lattice:
    top: @simpleOutput @simpleInput
    bottom: @simpleNoIO

pointcuts:
        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 (project is included with examples of use):

mkdir playground

We need to link the compiler libraries to our new project. At the root of the new project:

cd playground
mkdir lib

ln -s [Absoule-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

We could also have copied the libraries but, modifications to the discipline would implied a copy after every modification.

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.3" )


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 folders “src/main/scala/”

helloGE.scala looks like this:

import scala.annotation.effects._
 
object HelloGradualEffects{
 
  def main(args: Array[String]): Unit @simpleOutput = {
    println("hello gradual effects")
  }
}

We only have permissions 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()
  }

Which gives a static error. Now let us test gradual effects:

  def main(args: Array[String]): Unit @simpleOutput @pure= {
    def foo: Int @unknown = {
      readInt()
    }
    println("hello gradual effects")
    foo
  }

This code gives a runtime error: Not enough privileges simpleInput.

Modifying a discipline

Let us modify the simpleIO discipline defined in the previous section. Let us remember that the file can be found in examples/simpleIO.eff.

To modify the discipline, just edit the simpleIO.eff file, and then run

cd examples
./updateSimpleIODomain

Let us look at updateSimpleIODomain:

First it runs a script that uses effscript to generate the classes needed for the compiler plugins:

/usr/share/scala/bin/scala -cp ../effscript/target/scala-2.10/effscript_2.10-0.1-SNAPSHOT.jar updateDomain.scala simpleIO.eff

then it copies the generated files into each of the compiler plugins, overriding the existing implementations with the new ones:

cp target/SimpleIO.scala ../gpes/src/main/scala/annotation/effects/SimpleIO.scala

cp target/SimpleIO.scala ../efftp/src/main/scala/scala/annotation/effects/SimpleIO.scala

cp target/SimpleIODomain\(check\).scala ../gpes/src/main/scala/simpleIO/SimpleIODomain.scala

cp target/SimpleIODomain\(infer\).scala ../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/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).

Play project with effects

To illustrate a simple example of using effects with a Play application we provide an example project:

cd play-slick-advanced-effects
./activator

Once inside the activator console type run to run the application. The url to check the application running is http://localhost:9000

See app/controllers/EffectController.scala and app/views/index.scala.html for more information.

Running Benchmarks

For benchmarks, it is better to run the code not using sbt because using sbt the execution is slower. Benchmarks are inside benchmarks folder. 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 system
  • CollsSimple/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.