Skip to content

Adjustable Algorithms

twmarshall edited this page Sep 10, 2014 · 1 revision

In TBD, adjustable algorithms are written using our library.

#Mod

Every value in the algorithm which may change as the result of changes to the input must be placed into a modifiable. To create a new modifiable, you must call the 'mod' library function:

def mod[T](initializer: => Changeable[T]): Mod[T]

'mod' takes a single parameter, a block that initializes the value of the mod, and then returns the modifiable. For those unfamiliar with Scala syntax, the '=>' before the type of the argument means that it will be used as a call-by-name parameter, rather than call-by-value.

This means that the parameter will not be evaluated until it is used by 'mod', which allows us to specify the parameter as a block while ensuring that 'mod' can do some necessary set-up work before the block is run.

The parameter to 'mod' has type 'Changeable', and the only way to create a Changeable is to call write:

write[T](value: T): Changeable[T]

Changeables do not have any interpretable value on their own. They simply ensure that the initializer passed to mod ends in a write. When write is called, the mod that is written into is the one created by the call to mod that is closest in enclosing scope to the write, which will also be the call the write's Changeable is returned to.

Thus, a typical creation of a mod would look like:

val mod1 = mod {
  // some code
  write(value1)
}

after which, 'mod1' would contain 'value1'.

#Read

To access the contents of a modifiable, you must call 'read' on it:

def read[T, U](mod: Mod[T])(reader: T => Changeable[U]): Changeable[U]

Read takes a modifiable and a reader, a function takes the value of the modifiable as a parameter and returns a Changeable. Since the value of the modifiable is only accessible inside the reader function, when the modifiable is updated TBD only has to reexecute the reader to update the computation.

Read returns a changeable so that the reader can write into a modifiable that is declared outside of the read. This allows the reader to return a value to the rest of the algorithm while propagating changes to any code that relies on the return value if it changes as the result of the reader being reexecuted.

Scala supports several different formats for specifying function values, and any may be used with read as the situation calls for. However, in general case statements are preferred, as this is an elegant way to specify the reader as a block while binding variable names. A typical call to read:

val mod2 = mod {
  read(mod1) {
    case null =>
      // some code
      write(someReturn)
    case value =>
      // some other code
      write(someOtherReturn)
  }
}

#Par

To allow adjustable algorithms to be parallel, the library has a 'par' function. Par takes two functions as parameters and evaluates them in parallel.

Each of the functions takes a 'Context' as a parameter - a Context allows the library functions to know which Worker they are executing on. Each of the library functions takes a Context as an implicit parameter (in Scala, an implicit parameter doesn't have to be specified in the parameter list as long as there is an implicit object of the correct type in scope).

par[T, U](left: Context => T, right: Context => T)

#Adjustable

To actually define an adjustable algorithm that TBD can run, you must extend the 'Adjustable' trait:

trait Adjustable[T] {
  def run(implicit c: Context): T
}

For example, the following program takes a modifiable and outputs another modifiable that will always have a value twice that of the input value.

class Example(modA: Mod[Int]) extends Adjustable[Mod[Int]] {
  def run(implicit c: Context) = {
    mod {
      read(modA) {
        case value => write(value * 2)
      }
    }
  }
}
Clone this wiki locally