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 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() => restrict @simpleNoIO
        def views.html.foo.apply[T]() => restrict @simpleNoIO
        call scala.Predef.read*: T => set @simpleInput
        call fakePrint(T <: String) => set @simpleOutput
        call readM => set @simpleInput
        def scala.Predef.read*(V): T => set @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 do @simpleOutput. We can test this by calling method “readInt”:

  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 “goes” 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 benchmark run like this:

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