Composable Interactivity

Motivation

Partake (working title) is a domain-specific programming language for interactive systems such as GUIs and turn-based games. It’s an experiment to see if software can be composed using principles similar to those behind complex board games like Magic: the Gathering, Spirit Island, Dominion, …

Board games have a few properties that are also appealing properties for software:

Briefly, Partake is a logic programming language augmented by a relational I/O model and a simple reified notion of time. A primary goal is to facilitate natural expression of complex choice spaces. Our approach is to rely on relational queries to both specify game situations and the choices available to an actor. An actor is any external entity capable of making choices; this includes players, sources of randomness, and algorithms (Input). Actors observe the game state, which is represented as a set of relational facts (Output).

Comparisons

GUI Architectures

Model–View–Controller (MVC)

Partake is intended as a first step towards a comprehensive set of tools for authoring, playing, and modifying interactive software. These tools can be mapped using the model–view–controller terminology:

Model: Partake programs are essentially models in the MVC terminology. They formally define the schema of the application state and how the state can evolve.

View: There is a pattern for relational schemas that lends itself to simple view definitions: a set of unary entity predicates are used to distinguish different classes of entity that should be depicted; unary property predicates are used to apply temporary styling to entities; and binary relations such as located and adjacent specify basic spatial relationships. This sort of view might be automatically generated from an annotated program.

Controller: There are two aspects to a controller: the logical dynamics of choice, and the mundane binding code that links system I/O to those logical choices. Partake programs specify the former. There is also a generic formula for the latter implied by our I/O model: when choices arise, present a menu to the appropriate actor. In practice, most programmers will want to customize this to make choice selection more naturally aligned with the visualization of the game state.

React, Incremental

React is a highly popular “immediate mode”-style library for developing interactive GUIs in Javascript. It intermingles display and input, relying on JS as the host language for both. In comparison, Partake is a more opinionated form for interaction design. It benefits from a separate library (such as React) to implement the visualization layer of the application.

Incremental is a library for general incremental computation in OCaml. It extends the incremental DOM evaluation strategy of React to the entire computation. This suggests a certain conceptually simple model of application development: fold updates into a single value that provides the source of truth, then apply various functions to this source to compute the view; rely on incrementalization to make this efficient.

Partake avoids a certain “inversion of control” that may arise when defining GUIs this way. See Semantics Sketch below.

Morphic

Programming Models

todo

Logic Programs

Process Calculi

Programming Languages

todo

GDL

Riffle

Verse

Inform7, Puzzlescript

Implementation

Our prototype implements the basic language components: relational observation, choice, and temporal episodes with concurrency.

Semantics Sketch

Nearly all major languages descend from algorithmic ones and therefore use procedure/function composition as a dominant feature. However, interactions are processes consisting of concurrent actions taken by one or more agents across time, and they call for different composition methods.

A Partake program is a set of rules that communicate via manipulations of relational state. Each rule has three parts: an episode identifier called the trigger, a query, and an episode expression. Here is an example program fragment:

trigger: query Var, !do next-episode.
next-episode: ...

An episode is an interval of time with local state attached. Episodes can be composed sequentially or concurrently, and can contain sub-episodes. Episodes are meant to reflect a user’s mental model of “what is happening right now”. They provide a scope for local state and a basis for composing rules.

Actor choice points are embedded within episodes, so they arise “in the middle of the action”. This is meant to encourage certain UI practices:

todo explain syntax

Code Sample

# This program implements a turn of a game where
# one or more players draft from a deck and then choose
# a card to play.

# create the players, play areas, and three cards
game: +(
    player _, player _,
    play-area _,
    discard _,
    deck D,
    card X1, located X1 D,
    card X2, located X2 D,
    card X3, located X3 D,
  ),
  # each player has a hand and a drafting area
  (player P, +(hand P _, choose-area P _)),
  !do turn.

# to move `it`, change its location to `to`
move: -(located .it _), +(located .it .to).

# players first grow, then activate cards
turn: !do (grow-stage -> play-stage).

  # This query matches every `player`, then spawns concurrent
  # episodes for their `grow -> play` turn sequence.
  # Actions within this sequence can be arbitrarily interleaved:
  # for instance, we might deal three cards to each player
  # and only then make the choice of which to take.
  grow-stage: player P, !do [(grow -> play) | the-player P].

    # to grow, dealer deals cards, then player chooses one of three
    grow: !do (deal-cards -> choose-card).

      # nb: the `deal-cards` episodes ought to execute serially
      # to avoid invalid states.
      deal-cards:
        'rand chooses ~3 (located C .deck),
        !do [move | it C, to .the-player.choose-area].

      # choose 1, move rest to discard
      choose-card: the-player P,
        ('player chooses 1 (located C P.choose-area),
         !do [move | it C, to P.hand]),
        (located C' P.choose-area,
        !do [move | it C', to .discard]).

    # player moves one card from hand to the play area
    play:
      'player chooses ~1 (located C .the-player.hand),
      !do [move | it C, to .play-area].

  play-stage: located C .play-area, !do [activate | the-card C].

  # todo: activate

Here’s a partial screenshot of its evaluation in action: a screenshot of the preceding program being evaluated.

Repository

https://github.com/kovach/partake

https://cutfree.net