Graftid

From Impredicative Wiki
Revision as of 09:47, 4 February 2010 by Adam Chlipala (Talk | contribs)

Jump to: navigation, search

Graftid is a platform for developing and hosting web applications using Ur/Web, targeted at both programmers and the general public. These two audiences naturally need very different views of the service. The standard view is meant to be self-explanatory; this wiki page documents the picture for developers.

Graftid is in a closed alpha right now. The main web site is password-protected, while hosted applications can be accessed by anyone. If you are interested in testing, please e-mail Adam to ask for access. Note that things are slightly complicated so that testing can exercise the account creation code: the initial password you get will allow you into the site, but once there you will still need to create a Graftid account.

Contents

Overview

At a high level, Graftid is a repository of application designers, which are like browser-based GUI wizards for building particular kinds of web applications. Developers upload designers, which are written in Ur/Web and also generate Ur/Web code. Ideally these designers do very little ad-hoc code generation, instead serving merely to build calls to metaprogramming libraries, which can be implemented in Ur/Web such that static types guarantee they work properly on any valid inputs. To layperson end users, these details are invisible. They just see web forms that can be used to bring new database-backed web applications into existence.

Graftid can store a tree of libraries, owned by different users and with different versions available at once. This tree is much like a standard filesystem. Its main aspects are:

  • Users, corresponding to accounts created by people
  • Groups, named sets of users
  • Directories, in the usual UNIX-y sense, with every directory but the root directory having another directory as its parent. Different users and groups may be assigned read, write, or admin rights on a directory. Admin rights make it possible to edit the permission matrix. Groups themselves belong to directories, so that directory permissions control who may modify a group's member set. All of the following kinds of things that belong to directories have similar inheritance of directory permissions.
  • Libraries, named groups of Ur/Web modules, situated in directories
  • Library versions, particular sets of source files that may be linked to build concrete applications. Each version is associated with a particular library, but no formal connection is enforced between different versions of one library.
  • Designers, GUIs for building applications from particular libraries, situated in directories
  • Designer versions
  • Applications, specific persistent applications, situated in directories
  • Application versions

Programming GUIs

To get started coding Graftid application designers, you will want to download and install the GUI library. This library defines some types and module signatures that you will need to use or implement. First, since the mission of a designer is to generate Ur/Web code, we have the Ur module, which gives a representation of a subset of Ur/Web syntax.

(* Constructors, i.e., type-level data *)
datatype constr =
             TFun of constr * constr
             (* The usual function arrow *)
           | TRecord of constr
             (* Builds a record type from a constructor of soome kind {K} *)

           | CVar of list string * string
             (* Variable reference, with CVar (m1 :: ... :: mn :: nil, x)
              * meaning m1.[...].mn.x (for module names mi) *)
           | CApp of constr * constr
             (* Constructor-level function application *)

           | CName of string
             (* Field name literal *)

           | CRecord of list (constr * constr)
             (* Explicit record fields, as pairs of names and values *)

           | CUnit
             (* The trivial constructor *)

           | CTuple of list constr
             (* Constructor-level tuple construction *)

(* Value-level constants *)
datatype prim =
         Int of int
       | Float of float
       | String of string
       | Char of char

(* Value-level expressions *)
datatype exp =
         EPrim of prim
         (* Constant *)
       | EVar of list string * string
         (* Variable reference, encoded as for CVar *)
       | EApp of exp * exp
         (* Function application *)
       | EAbs of string * exp
         (* Function abstraction *)
       | ECApp of exp * constr
         (* Applying a polymorphic function *)
       | EDisjointApp of exp
         (* Applying an abstraction over a row disjointness constraint *)
       | ERecord of list (constr * exp)
         (* Record construction *)
       | EField of exp * constr
         (* Record projection *)
       | ELet of string * exp * exp
         (* Local definition *)

(* Declarations *)
datatype decl =
         DCon of string * constr
         (* Constructor synonym *)
       | DVal of string * exp
         (* Value-level binding *)
       | DValRec of list (string * exp)
         (* Mutually-recursive value bindings (each exp must begin with EAbs) *)
       | DStr of string * str
         (* Structure (i.e., module) definition *)
       | DOpen of list string * string
         (* Import a module path *)

(* Modules *)
     and str =
         StrConst of list decl
         (* Structure with known contents *)
       | StrVar of string
         (* Variable reference *)
       | StrProj of str * string
         (* Project out a sub-structure *)
       | StrApp of str * str
         (* Apply a functor *)

type file = list decl

The other piece of the picture is a signature for widgets, which are the building blocks of designer GUIs. They come from the Gui module.

(* Signature for widgets that may be used in designers *)
signature WIDGET = sig
    type ast
    (* What sort of syntax does this widget generate? *)

    type widget
    (* Internal state type *)

    type persistent
    (* Persistent state type, to serialize/deserialize for long-term storage *)

    val default : persistent
    (* What state should a fresh widget of this variety have? *)
    val create : persistent -> transaction widget
    (* Initialize an ephemeral version of some persistent state. *)

    val persistent : widget -> signal persistent
    (* Use this to listen for changes to a widget's persistent state. *)
    val push : widget -> persistent -> transaction unit
    (* Update an existing widget to match some new persistent state. *)

    val render : widget -> xbody
    (* Display the GUI for a widget. *)
    val generate : persistent -> ast
    (* Translate state into Ur syntax. *)

    val testCase : persistent
    (* A good example state to use in testing that the widget is implemented
     * properly.  When you upload your widget, Graftid will run a test
     * compilation using this state, rejecting your upload if anything goes
     * wrong. *)
end

signature EWIDGET = WIDGET where type ast = Ur.exp
signature FWIDGET = WIDGET where type ast = Ur.file

Setting Up a Development Environment

Grab the grmake script and install it somewhere in your PATH. Edit the file ~/.graftidrc and add definitions for the following variable, on separate NAME=VALUE lines.

  • GUI: the directory where you installed the GUI library
  • MODULES: a directory where grmake should maintain a local cache of the source to libraries pulled from the Graftid web site

The curious can poke around the grmake source to find some other variables that you might want to set, but these two should be enough to get started. You will also need to have the normal Ur/Web distribution installed.

Examples

Hello World

Let's build a module for a family of web applications that just print "Hello" followed by an arbitrary string. Pick a directory where you will develop the source and create a file hello.urs with this content:

val main : string -> unit -> transaction page

In other words, given a string to display after "Hello", our main function returns an entry point to the application.

Now create hello.ur:

fun main s () = retun <xml>Hello {[s]}!</xml>

Finally, create lib.urp, the project file tying together the Ur sources:

hello

Note that an empty line at the beginning is required.

Now we are ready to try building our library.

$ grmake
/dir/hello.ur:1:16-1:21: Unbound expression variable retun

Oops! We made a typo in the hello.ur code. Change retun to return and try again.

$ grmake

This time no messages are printed, so our library checked out. Now we can package it for uploading to Graftid.

$ grmake package

A file lib.tgz appears in the current directory. To upload it, log into Graftid and follow the "Home" link in the top toolbar of any page. This brings you to the main page for your user's home directory. Under the "Libraries" heading, follow the "New library" link. On that page, choose "hello" as the library name and enter whatever descriptions you want for the library overall and for this first version of it. Select the lib.tgz file we just built as the package to upload. Submitting the form should create the library quickly, and it will then show up in your home directory's "Libraries" list.

Personal tools