Functional Reactive UI Thing

James Earl Douglas

November 14, 2011

Example

Consider a simple Swing application with a couple of combo boxes and some labels.

Example

Consider a simple Swing application with a couple of combo boxes and some labels.

Changing the selection of the combo boxes updates the text in the labels.

Implementation in Java

Implementation in Java

private String label3Prefix = "";
private String label3Suffix = "";

private String label4Prefix = "";
private String label4Suffix = "";

Implementation in Java

private String updateLabel3Prefix(String value) {
  label3Prefix = value;
  return label3Text();
}

private String updateLabel3Suffix(String value) {
  label3Suffix = value;
  return label3Text();
}

private String updateLabel4Prefix(String value) {
  label4Prefix = value;
  return label4Text();
}

private String updateLabel4Suffix(String value) {
  label4Suffix = value;
  return label4Text();
}

Implementation in Java

private String label3Text() {
  return label3Prefix + " is " + label3Suffix;
}

private String label4Text() {
  return label4Prefix + " ain't " + label4Suffix;
}

Implementation in Java

private void combo1Updated() {
  String value = combo1.getSelectedItem().toString();
  label1.setText(value);
  label3.setText(updateLabel3Prefix(value));
  label4.setText(updateLabel4Prefix(value));
}

private void combo2Updated() {
  String value = combo2.getSelectedItem().toString();
  label2.setText(value);
  label3.setText(updateLabel3Suffix(value));
  label4.setText(updateLabel4Suffix(value));
}

Implementation in Java

combo1.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    combo1Updated();
  }
});

combo2.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    combo2Updated();
  }
});

Implementation in Java

Holy spaghetti code, Batman!

Functional Reactive Programming

scala> val combo1 = new JComboBox(Array[AnyRef]("Fred", "Sam", "Joe"))
combo1: javax.swing.JComboBox = ...

scala> println("combo1 is: " + signal(combo1))
combo1 is: Fred

scala> combo1.setSelectedIndex(0)
combo1 is: Fred

scala> combo1.setSelectedIndex(1)
combo1 is: Sam

scala> combo1.setSelectedIndex(2)
combo1 is: Joe

Implementation in Scala

fruit { implicit s: Signals =>
  label1.setText(signal(combo1))
  label2.setText(signal(combo2))
  label3.setText(signal(combo1) + " is " + signal(combo2))
  label4.setText(signal(combo1) + " ain't " + signal(combo2))
}

Fruit

class Signal(selectable: Selectable) {

  private var c: Option[String => Unit] = None

  selectable.addActionListener(new ActionListener() {
    def actionPerformed(e: ActionEvent) {
      c.foreach(_(selectable.getSelectedItem.toString))
    }
  })

  def apply(k: (String => Unit)) {
    c = Option(k)
    k(selectable.getSelectedItem.toString)
  }
}

Fruit

trait Fruit {

  type Selectable = {
    def getSelectedItem(): AnyRef
    def addActionListener(a: ActionListener): Unit
  }

  val signals = collection.mutable.HashMap[Int, Signal]()

  def signal(selectable: Selectable, x: Int): String @suspendable =
    shift { k: (String => Unit) =>
      if (!signals.contains(x)) signals(x) = new Signal(selectable)
      signals(x)(k)
    }

  def fruit(f: => Unit @suspendable) = reset(f)
}

Peeking under the rug

A couple of approaches, each with issues

fruit { implicit s: Signals =>
  label1.setText(signal(combo1))
  label2.setText(signal(combo2))
  label3.setText(signal(combo1) + " is " + signal(combo2))
  label4.setText(signal(combo1) + " ain't " + signal(combo2))
}

References