mattpap / IScala

Scala backend for IPython
MIT License
322 stars 49 forks source link

IScala

IScala is a Scala-language backend for IPython.

Build Status

Requirements

Usage

First obtain a copy of IScala from here. The package comes with pre-compiled IScala.jar and collection of scripts for running IPython's console, qtconsole and notebook. IScala.jar contains all project dependencies and resources, so you can move IScala easily around. To start IPython's console, simply issue bin/console in a terminal. This will start ipython console and setup it to use IScala backend instead of the default Python one. Issue bin/qtconsole or bin/notebook to start IPython's Qt console or notebook, respectively.

To start IPython with IScala backend manually, issue:

ipython console --KernelManager.kernel_cmd='["java", "-jar", "lib/IScala.jar", "--connection-file", "{connection_file}", "--parent"]'

The same works for qtconsole and notebook, and is, in principle, what scripts in bin/ do. Note that you may have to provide a full path to IScala.jar. Option --parent is important and tells IScala that it was started by IPython and is not a standalone kernel. If not provided, double ^C (INT signal) within 500 ms terminates IScala. Otherwise, TERM signal or shutdown message is needed to terminate IScala gracefully. As a safety measure, IScala also watches connection file that IPython provided. If the file is removed, the respective kernel is terminated.

You can also create a scala profile for IPython. To do this, issue:

$ ipython profile create scala
[ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_config.py'
[ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_qtconsole_config.py'
[ProfileCreate] WARNING | Generating default config file: u'~/.config/ipython/profile_scala/ipython_notebook_config.py'

Then add the following line:

c.KernelManager.kernel_cmd = ["java", "-jar", "$ISCALA_PATH/lib/IScala.jar", "--connection-file", "{connection_file}", "--parent"]"

to ~/.config/ipython/profile_scala/ipython_config.py. Replace $ISCALA_PATH with the actual location of IScala.jar. Then you can run IPython with ipython console --profile scala.

To start a standalone kernel simply issue:

$ java -jar lib/IScala.jar
connect ipython with --existing kernel-18271.json
Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27)

This creates a connection file kernel-PID.json, where PID is the process ID of IScala kernel. You can connect IPython using --existing kernel-PID.json. You can provide an existing connection file with --connection-file option.

IScala supports other options as well. See java -jar IScala.jar -h for details. Note that you can also pass options directly to Scala compiler after -- delimiter:

$ java -jar IScala.jar --connection-file kernel.json -- -Xprint:typer

This will start standalone IScala with preexisting connection file and make Scala compiler print Scala syntax trees after typer compiler phase.

Example

$ bin/console
Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27)

In [1]: 1
Out[1]: 1

In [2]: 1 + 2 + 3
Out[2]: 6

In [3]: (1 to 5).foreach { i => println(i); Thread.sleep(1000) }
1
2
3
4
5

In [4]: val x = 1
Out[4]: 1

In [5]: x
Out[5]: 1

In [6]: 100*x + 17
Out[6]: 117

In [7]: x.<TAB>
x.%             x.-             x.>>            x.isInstanceOf  x.toFloat       x.toString      x.|
x.&             x./             x.>>>           x.toByte        x.toInt         x.unary_+
x.*             x.>             x.^             x.toChar        x.toLong        x.unary_-
x.+             x.>=            x.asInstanceOf  x.toDouble      x.toShort       x.unary_~

In [7]: x.to<TAB>
x.toByte    x.toChar    x.toDouble  x.toFloat   x.toInt     x.toLong    x.toShort   x.toString

In [7]: x.toS<TAB>
x.toShort   x.toString

In [7]: 1/0
java.lang.ArithmeticException: / by zero

In [8]: java.util.UUID.fromString("abc")
java.lang.IllegalArgumentException: Invalid UUID string: abc
    java.util.UUID.fromString(UUID.java:226)

In [9]: class Foo(a: Int) { def bar(b: String) = b*a }

In [10]: new Foo(5)
Out[10]: Foo@70f4d063

In [11]: _10.bar("xyz")
Out[11]: xyzxyzxyzxyzxyz

In [12]: import scala.language.experimental.macros

In [13]: import scala.reflect.macros.Context

In [14]: object Macros {
    ...:     def membersImpl[A: c.WeakTypeTag](c: Context): c.Expr[List[String]] = {
    ...:         import c.universe._
    ...:         val tpe = weakTypeOf[A]
    ...:         val members = tpe.declarations.map(_.name.decoded).toList.distinct
    ...:         val literals = members.map(member => Literal(Constant(member)))
    ...:         c.Expr[List[String]](Apply(reify(List).tree, literals))
    ...:     }
    ...:
    ...:     def members[A] = macro membersImpl[A]
    ...: }
    ...:

In [15]: Macros.members[Int]
Out[15]: List(<init>, toByte, toShort, toChar, toInt, toLong, toFloat, toDouble, unary_~,
unary_+, unary_-, +, <<, >>>, >>, ==, !=, <, <=, >, >=, |, &, ^, -, *, /, %, getClass)

Magics

IScala supports magic commands similarly to IPython, but the set of magics is different to match the specifics of Scala and JVM. Magic commands consist of percent sign % followed by an identifier and optional input to a magic. Magic command's syntax may resemble valid Scala, but every magic implements its own domain specific parser.

Type information

To infer the type of an expression use %type expr. This doesn't require evaluation of expr, only compilation up to typer phase. You can also get compiler's internal type trees with %type -v or %type --verbose.

In [1]: %type 1
Int

In [2]: %type -v 1
TypeRef(TypeSymbol(final abstract class Int extends AnyVal))

In [3]: val x = "" + 1
Out[3]: 1

In [4]: %type x
String

In [5]: %type List(1, 2, 3)
List[Int]

In [6]: %type List("x" -> 1, "y" -> 2, "z" -> 3)
List[(String, Int)]

In [7]: %type List("x" -> 1, "y" -> 2, "z" -> 3.0)
List[(String, AnyVal)]

Library management

Library management is done by sbt. There is no need for a build file, because settings are managed by IScala. To add a dependency use %libraryDependencies += moduleID, where moduleID follows organization % name % revision syntax. You can also use %% to track dependencies that have binary dependency on Scala. Scala version used is the same that IScala was compiled against.

To resolve dependencies issue %update. If successful this will restart the interpreter to allow it to use the new classpath. Note that this will erase the state of the interpreter, so you will have to recompute all values from scratch. Restarts don't affect interpreter's settings.

In [1]: import scalaj.collection.Imports._
<console>:7: error: not found: value scalaj
       import scalaj.collection.Imports._
              ^

In [2]: %libraryDependencies += "org.scalaj" %% "scalaj-collection" % "1.5"

In [3]: %update
[info] Resolving org.scalaj#scalaj-collection_2.10;1.5 ...
[info] Resolving org.scala-lang#scala-library;2.10.2 ...

In [1]: import scalaj.collection.Imports._

In [2]: List(1, 2, 3)
Out[2]: List(1, 2, 3)

In [3]: _2.asJava
Out[3]: [1, 2, 3]

In [4]: _3.isInstanceOf[List[_]]
Out[4]: false

In [5]: _3.isInstanceOf[java.util.List[_]]
Out[5]: true

In [6]: %libraryDependencies
List(org.scalaj:scalaj-collection:1.5)

If a dependency can't be resolved, %update will fail gracefully. For example, if we use com.scalaj organization instead of org.scalaj, then we will get the following error:

In [1]: %libraryDependencies += "com.scalaj" %% "scalaj-collection" % "1.5"

In [2]: %update
[info] Resolving com.scalaj#scalaj-collection_2.10;1.5 ...
[warn]  module not found: com.scalaj#scalaj-collection_2.10;1.5
[warn] ==== sonatype-releases: tried
[warn]   https://oss.sonatype.org/content/repositories/releases/com/scalaj/scalaj-collection_2.10/1.5/scalaj-collection_2.10-1.5.pom
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  ::          UNRESOLVED DEPENDENCIES         ::
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[warn]  :: com.scalaj#scalaj-collection_2.10;1.5: not found
[warn]  ::::::::::::::::::::::::::::::::::::::::::::::
[error] unresolved dependency: com.scalaj#scalaj-collection_2.10;1.5: not found

By default IScala uses Sonatype's releases maven repository. To add more repositories use %resolvers += "Repo Name" at "https://path/to/repository" and run %update again.

Development

Obtain a copy of IScala either by cloning this repository or download it from here. We use SBT for dependency management, compilation and deployment. In a terminal issue:

$ cd IScala
$ ./sbt

This will start SBT console (which will be indicated by > prefix). On first run SBT will download itself, its dependencies and plugins, and compile project build file. From here you can compile the project by issuing compile command:

> compile

It implicitly run update task, so on first run it will download all project dependencies (including Scala standard library and compiler), so it may take a while. Note that dependencies are cached in ~/.ivy2 directory, so they will be picked up next time SBT is run (also in other projects compiled from the same account).

Ignore any (deprecation) warnings you will get. To start IScala issue:

> run
[info] Running org.refptr.iscala.IScala
[info] connect ipython with --existing kernel-18271.json
[info] Welcome to Scala 2.10.2 (OpenJDK 64-Bit Server VM, Java 1.6.0_27)

This is an equivalent of starting a standalone IScala kernel from a terminal. To terminate a kernel press Ctrl+C (SBT my signal an error). Finally to generate a JAR file with IScala's class files, resources and dependencies, issue assembly. You can run it with:

$ java -jar IScala.jar

Unless you made any changes, this is exactly the JAR you can download from IScala's releases page.

Status

This is an early work in progress. Main features and majority of IPython's message specification were implemented, however certain features are not yet available (e.g. introspection) or are limited in functionality and subject to major changes. Report any problems and submit enhancement proposals here.

Acknowledgment

This work is substantially based on IJulia, a Julia-language backend for IPython.

License

Copyright © 2013-2014 by Mateusz Paprocki and contributors.

Published under The MIT License, see LICENSE.