sp5repl: A Read-Eval-Processing Loop with Scala

I teach two introductory computing classes at Bard: one using Python (using IPRE’s Calico and robots) and the other with Processing. Both programming environments could be better by borrowing ideas from the other. And by better, I mean a lower floor, making it easier for newcomers to programming; and a higher ceiling, making the tool useful after CS1. Rather than concentrating exclusively on one tool, I am continuing to attack the problem on both fronts.

This post is focused on making Processing better for introductory courses; Calico is next.

My first attempt is a simple tool called sp5repl, a small layer around Scala and Processing that allows you to write Processing sketches dynamically using an interactive read-eval-loop. The code entered into the Scala REPL is actually compiled, thus it runs at full speed; we get most of the flexibility of Jython and Clojure/Quil with the speed and error checking of Scala. A small example that generates the image below:

sp5repl>size(250, 250)
sp5repl>background(24)
sp5repl>smooth()
sp5repl>fill(196, 128, 64)
sp5repl>ellipse(width/2, height/2, 150, 150)
sp5repl>fill(64, 128, 196)
sp5repl>for (i <- width to 0 by -1) ellipse(random(i), i, i/20, i/20)

screen4

I really like Python for CS-1. Python's interactive read-eval-print loop (REPL) is helpful for students building an intuition for programming. More generally, the REPL is useful for quick calculations, debugging, prototyping, and testing. Python's easy-to-use lists, dictionaries, and libraries make many tasks simple enough for CS-1, e.g., slurping in files and doing text analysis. Python's dynamic typing and syntax make programs succinct and there is no boilerplate I-promise-you-will-understand-this-later code.

On the other hand, Python can be quite slow. Yeah, yeah, I know, this shouldn't matter pedagogically. But students want to write real programs, programs that do real things like interact with webcams and their photo albums. Python makes things like live image processing problematic. Image processing tasks like convolution are no problem in Processing because it compiles to Java. And along with smart default values for things, a large collection of libraries, and the great community around Processing, it is a nice choice for introducing computing. Students have written really interesting programs in one semester. Sadly, it is still Java -- which in my experience teaching introductory students, has lots of baggage compared with Python. Java's obtuse syntax, explicit type system, lack of operator overloading, and OO-only approach confuse introductory students. And obviously, no REPL. Although I guess you could use Dr. Java or BlueJ with Processing, but you're still dealing with full-blown Java.

Personally, so far anyway, I have found sp5repl to be a good compromise between Processing and Python and maybe a good statically typed replacement for Python. It still has a little extra punctuation and boiler-plate, but the curly brace delimited blocks aren't as common since everything is an expression and the type inference removes lots of redundancy. Of course, the higher-order functions and for comprehensions make programs pretty, but at the risk of sacrificing introductory-level readability. And, the static typing catches lots of bugs.

Advantages

  • Interactively construct programs, like Python or Scheme.
  • Interact with the Processing sketch at run-time (useful for debugging, testing, and configuration).
  • The REPL actually compiles code so there is no performance penalty.
  • Scala's compiler catches type errors early.
  • All of Processing's libraries are available. (e.g. GSVideo for grabbing webcam data)
  • sp5repl is a single scala file, and a couple of shell scripts to setup the classpath; super simple.

Disadvantages

  • Lack of community. Compared with Python or Processing which both have huge communities the Scala community is tiny. sp5repl is even smaller 😉
  • Some of the fancy functional programming tricks in Scala might be a little too much for CS1. This topic is debated endlessly e.g., scheme vs. java or python, but I'll just stop there.
  • Lack of a nice IDE for introductory students. I've used Emacs and Sublime Text, but they aren't particularly targeted at the novice user. [This is exactly where Calico excels.]
  • The hyper-imperative style of Processing isn't very idiomatic of Scala's functional OO style.
  • (Sometimes I feel like Scala is C++ for the JVM and I cringe ...)

An Example: Word Frequency

One task we do late in my introductory class that uses Python is compute word frequencies. The following examples count the number of occurrences of each word in Moby Dick. This is pretty easy with Python's dictionaries:

f = open('mobyclean.txt', 'r')
words = f.read()
words = words.lower().replace(". ", " ").replace(", ", " ").split()
f.close()

wordcount = {}
for word in words:
    if w in wordcount:
        wordcount[w] = wordcount[w] + 1
    else:
        wordcount[w] = 1

This task, and interacting with text more generally, is a real pain in Java. Processing's loadStrings() makes things a little better, but it is still cumbersome when compared with Python. Here's the word count example in python-esque Scala:

import scala.io._

val s = Source fromFile("mobyclean.txt") mkString
val words = s replace (",", " ") replace (".", " ") split("\\s+")

var wordcount: Map[String, Int] = Map()
for (w <- words)
  if (wordcount.contains(w)) 
    wordcount += w -> (wordcount(w) + 1)
  else 
    wordcount += w -> 1

And a more Scala-esque version along with a Processing visualization:

import scala.io._
import scala.util.Random

size(800, 1200)
background(0)
noStroke()
fill(255)
textSize(10)

val s = Source fromFile("mobyclean.txt") mkString
val words = s replace (",", " ") replace (".", " ") split("\\s+")
val counts = words groupBy(x=>x) mapValues(x=>x.length)
val counts_sorted = counts.toList sortBy {_._2} reverse

def drawCounts() = {
  background(24)
  val (_, max) = counts_sorted(0)
  var y = 12
  for ((w, c) <- counts_sorted if c > 1){
    val x2 = map(c, 0, max, 75, width - 120)
    fill(255, 0, 0)
    rect(75, y-8, x2, 10)

    fill(255)
    textAlign(RIGHT)
    text(w, 70, y)
    fill(128)
    textAlign(LEFT)
    text(c, x2 + 80, y)
    y = y + 12
  }
}
onDraw(drawCounts)

wordcount.png

Other Related Efforts

Using the IPRE robots

It is easy to use sp5repl with the IPRE robots using Doug Harms' Java library:

  import Myro._
  val scribbler = new Scribbler("/dev/tty.scribbler")
  import scribbler._
  println(getName())
  println (getBattery())
  turnRight(1, .5)
  takePicture().show()

and if you want to mix IPRE with Processing this little implicit conversion gem is useful:

implicit def MyroImage2PImage(p:MyroImage): PImage = {
  val bimg = p.getImage()
  val img = new PImage(bimg.getWidth(), bimg.getHeight(), PConstants.ARGB)
  bimg.getRGB(0, 0, img.width, img.height, img.pixels, 0, img.width)
  img.updatePixels()
  img
}

Leave a Reply

Your email address will not be published. Required fields are marked *