Slides from keynote at MOSE 2010, Malaga, June 29, 2010




          Zef Hemel             Eelco Visser


        @mobllang @zef @eelcovisser
WebDSL




  Domain-Specific
Language Engineering

SDF    Spoofax   Stratego
domain:
mobile applications
50 million    20 million


 iPhones     iPod Touches
1.5 million             1.2 million

    G1                       Droid


     outsells iPhone in US
application
development
Objective-C        Java          J2ME/C++




 HTML/Javascript          Java
Objective-C


Android Java

Blackberry
Java
J2ME

HTML/JS
3.3.1
3.3.1 – Applications may only use Documented
APIs in the manner prescribed by Apple and
must not use or call any private APIs.
Applications must be originally written in
Objective-C, C, C++, or JavaScript as executed
by the iPhone OS WebKit engine, and only code
written in C, C++, and Objective-C may compile
and directly link against the Documented APIs
(e.g., Applications that link to Documented APIs
through an intermediary translation or
compatibility layer or tool are prohibited).
AppStore
cross-platform development


   arbitrary rejections


we want high-level models
Webkit
HTML
WebDatabases
                 Full-screen support
Location information
       (GPS)
                       Offline support
         Canvas

  Multi-touch            Threading
“   We believe the web has won and over the next
    several years, the browser [..] will become the
    platform that matters and certainly that’s where
    Google is investing.
                                                                ”
                            Vic Gundotra, Google VP of Engineering
syntax similar to
data model
  user interface
      script
web service access
data model
entity Task {
  name        : String (searchable)
  done        : Bool
  dueDate     : DateTime
}
entity Task {
  name          :   String (searchable)
  done          :   Bool
  dueDate       :   DateTime
  categories    :   Collection<Category>
}

entity Category {
  name        : String
  tasks       : Collection<Task> (inverse: categories)
}
user interface
screen root() {
  header("Todo")
  group {
    list(t in Task.all()) {
      item {
        checkbox(t.done)
        " "
        label(t.name)
      }
    }
  }
}
screen root() {
  header("Todo")
  topButton("Add", onclick={
     addTask();
  })
  group {
     list(t in Task.all()) {
       item {
         checkbox(t.done)
         " "
         label(t.name)
       }
     }
  }
}
screen addTask() {
  var newTask = Task {
    done = false,
    dueDate = now()
  }

    header("Add")
    backButton("Back", onclick={
       screen return;
    })
    group {
       item { textField(newTask.name) }
       item { datePicker(newTask.dueDate) }
    }
    button("Add", onclick={
       add(newTask);
       screen return;
    })
}
screen root() {
  header("Todo")
  topButton("Add", onclick={
     addTask();
  })
  group {
     list(t in Task.all()) {
       item {
         checkbox(t.done)
         " "
         label(t.name)
       }
     }
  }
}
screen root() {
  var query = ""

    header("Todo")
    topButton("Add", onclick={
      addTask();
    })
    searchBox(query)
    group {
       list(t in Task.search(query)) {
         item {
           checkbox(t.done)
           " "
           label(t.name)
         }
      }
    }
}
scripting
function cleanDoneTasks() : Num {
  var removed = 0;
  for(t in Task.all()) {
    if(t.done) {
      remove(t);
      removed = removed + 1;
    }
  }
  return removed;
}
data binding
one-way


var n = 0

label(n)

button("Up", onclick={
   n = n + 1;
})
two-way


var n = 0

inputNum(n)
label(n)
reactive/dataflow programming
var amount = 10
var percentage = 10
var total <- amount *
  (1 + percentage/100)

inputNum(amount)
inputNum(percentage)
label(total)
web services
http://api.stackoverflow.com/0.8/questions?answers=true&body=true

{
    "total": 746646,
    "page": 1,
    "pagesize": 30,
    "questions": [
      {
        "tags": ["string", "assembly", "arm"],
        "answers": [],
        "question_id": 3092029,
        "owner": {
          "user_id": 320124,
          "user_type": "registered",
          "display_name": "SoulBeaver",
          "reputation": 195
        },
        "creation_date": 1277200629,
        "score": 0,
        "title": "ARM - Infinite Loop While Searching String",
        "body": "...",
        ...
      },
      ...
    ]
}
{
    "total": 746646,
    "page": 1,
    "pagesize": 30,
    "questions": [
      {
         "tags": ["string", "assembly", "arm"],
         "answers": [],
         "question_id": 3092029,
         "owner": {
            "user_id": 320124,
            "user_type": "registered",
            "display_name": "SoulBeaver",
            "reputation": 195
         },
         "creation_date": 1277200629,
         "score": 0,
         "title": "ARM - Infinite Loop While Searching String",
         "body": "...",
         ...
      },
      ...
    ]
}
                                              external type   QuestionsResultSet {
                                                total     :   Num
                                                page      :   Num
                                                pagesize :    Num
                                                questions :   Array<QuestionResult>
                                              }
{
    "tags": ["string", "assembly", "arm"],
    "answers": [],
    "question_id": 3092029,
    "owner": {
       "user_id": 320124,
       "user_type": "registered",
       "display_name": "SoulBeaver",
       "reputation": 195
    },
    "creation_date": 1277200629,
    "score": 0,
    "title": "ARM - Infinite Loop While Searching String",
    "body": "...",
    ...
}



                                            external type QuestionResult {
                                              tags          : Array<String>
                                              answers       : Array<AnswerResult>
                                              owner         : OwnerResult
                                              creation_date : Num
                                              ...
                                            }
service StackOverflow {
  root = "http://api.stackoverflow.com/0.8"
  resource questions(answers : Bool, body : Bool) : QuestionsResultSet {
    uri = "/questions"
    method = "GET"
    encoding = "json"
  }
  ...
}
function fetchQuestions() {
  var res = StackOverflow.questions(answers=true, body=true);
  for(question : QuestionResult in res.questions) {
    mapQuestion(question);
  }
}
entity Question {
  questionId   : Num
  title        : String
  body         : Text
  answers      : Collection<Answer> (inverse: question)
  creationDate : DateTime
  owner        : User
}

entity Answer {
  question        :   Question
  answerId        :   Num
  owner           :   User
  body            :   Text
}

entity User {
  userId     : Num
  name       : String
  reputation : Num
}
function mapQuestion(qr : QuestionResult) : Question {
  var q : Question = cachedQuestion(remote.question_id);
  if(q == null) {
    q = Question {
       questionId = qr.question_id,
       title = qr.title,
       body = qr.body,
       answers = mapAnswers(qr.answers),
       creationDate = DateTime.fromTimestamp(qr.creation_date),
       owner = mapUser(qr.owner)
    };
    add(q);
  }
  return q;
}
implementation
mobl code



   parse


   check


  desugar


 generate
   code


HTML/Javascript
entity Task {
          name        : String (searchable)
          done        : Bool
          dueDate     : DateTime
        }



                             Javascript using
                             persistence.js
                               HTML5 ORM


tasks.Task = persistence.define('tasks__Task', {
  'name': 'TEXT',
  'done': 'BOOL',
  'dueDate': 'DATE'
});
tasks.Task.textIndex('name');
screen root() {
                header("Todo")
                ...
              }



                           Javascript functions
                              building DOM


tasks.root = function(callback, screenCallback) {
   var root1018 = $("<div>");
   mobl.header(ref("Todo"), function(node) {
     root1018.append(node);
     ...
   });
};
function cleanDoneTasks() : Num {
       var removed = 0;
       for(t in Task.all()) {
         if(t.done) {
           remove(t);
           removed = removed + 1;
         }
       }
       return removed;
     }




tasks.cleanDoneTasks = function() {
  var removed = 0;
  var results = Task.all();
  for(var i = 0; i < results.length; i++) {
    var t = results[i];
    if(t.done) {
      remove(t);
      removed = removed + 1;
    }
  }
  return removed;
}
function cleanDoneTasks() : Num {
        var removed = 0;
        for(t in Task.all()) {
          if(t.done) {
            remove(t);
            removed = removed + 1;
          }
        }
        return removed;
      }




tasks.cleanDoneTasks = function() {
   var removed = 0;
   var results = Task.all();
   for(var i = 0; i < results.length; i++) {
     var t = results[i];
     if(t.done) {
       remove(t);
       removed = removed + 1;
     }
   }
   return removed;
};
tasks.cleanDoneTasks = function() {
   var removed = 0;
   var results = Task.all();
   for(var i = 0; i < results.length; i++) {
     var t = results[i];
     if(t.done) {
       remove(t);
       removed = removed + 1;
     }
   }
   return removed;
};

                             continuation-passing
                               style transform
tasks.cleanDoneTasks = function(callback) {
   var removed = 0;
   Task.all(function(results) {
     for(var i = 0; i < results.length; i++) {
       var t = results[i];
       if(t.done) {
         remove(t);
         removed = removed + 1;
       }
     }
     callback(removed);
   });
};
reactive programming
screen root() {
  var n = 8
  label(n * n)
  button("Inc", onclick={
     n = n + 1;
  })
}
var n = 8



       var n = ref(8);

Observable
- set(value)
- get()
- addEventListener(eventType, callback)
label(n * n)




var node565 = $("<span>");
node565.text(n.get() * n.get());
n.addEventListener("change", function() {
    node565.text(n.get() * n.get());
});
root.append(node565);
button("Inc", onclick={
            n = n + 1;
         })




var nodes566 = $("<span class='button'>");
node566.text("Inc");
node566.click(function() {
  n.set(n.get() + 1);
});
root.append(node566);
screen root() {
  var n = 8
  label(n * n)
  button("Inc", onclick={
     n = n + 1;
  })
}
conclusion
many mobile platforms


      HTML5/JS


avoid AppStore approval
statically-typed WebDSL-like language


        generates HTML/JS


CPS transform/reactive programming
get it?

http://mobl-lang.org




 http://spoofax.org

mobl

  • 1.
    Slides from keynoteat MOSE 2010, Malaga, June 29, 2010 Zef Hemel Eelco Visser @mobllang @zef @eelcovisser
  • 2.
    WebDSL Domain-Specific LanguageEngineering SDF Spoofax Stratego
  • 3.
  • 4.
    50 million 20 million iPhones iPod Touches
  • 5.
    1.5 million 1.2 million G1 Droid outsells iPhone in US
  • 7.
  • 8.
    Objective-C Java J2ME/C++ HTML/Javascript Java
  • 9.
  • 11.
  • 12.
    3.3.1 – Applicationsmay only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs. Applications must be originally written in Objective-C, C, C++, or JavaScript as executed by the iPhone OS WebKit engine, and only code written in C, C++, and Objective-C may compile and directly link against the Documented APIs (e.g., Applications that link to Documented APIs through an intermediary translation or compatibility layer or tool are prohibited).
  • 14.
  • 16.
    cross-platform development arbitrary rejections we want high-level models
  • 18.
  • 19.
  • 20.
    WebDatabases Full-screen support Location information (GPS) Offline support Canvas Multi-touch Threading
  • 22.
    We believe the web has won and over the next several years, the browser [..] will become the platform that matters and certainly that’s where Google is investing. ” Vic Gundotra, Google VP of Engineering
  • 25.
  • 26.
    data model user interface script web service access
  • 27.
  • 28.
    entity Task { name : String (searchable) done : Bool dueDate : DateTime }
  • 29.
    entity Task { name : String (searchable) done : Bool dueDate : DateTime categories : Collection<Category> } entity Category { name : String tasks : Collection<Task> (inverse: categories) }
  • 30.
  • 31.
    screen root() { header("Todo") group { list(t in Task.all()) { item { checkbox(t.done) " " label(t.name) } } } }
  • 32.
    screen root() { header("Todo") topButton("Add", onclick={ addTask(); }) group { list(t in Task.all()) { item { checkbox(t.done) " " label(t.name) } } } }
  • 33.
    screen addTask() { var newTask = Task { done = false, dueDate = now() } header("Add") backButton("Back", onclick={ screen return; }) group { item { textField(newTask.name) } item { datePicker(newTask.dueDate) } } button("Add", onclick={ add(newTask); screen return; }) }
  • 34.
    screen root() { header("Todo") topButton("Add", onclick={ addTask(); }) group { list(t in Task.all()) { item { checkbox(t.done) " " label(t.name) } } } }
  • 35.
    screen root() { var query = "" header("Todo") topButton("Add", onclick={ addTask(); }) searchBox(query) group { list(t in Task.search(query)) { item { checkbox(t.done) " " label(t.name) } } } }
  • 36.
  • 37.
    function cleanDoneTasks() :Num { var removed = 0; for(t in Task.all()) { if(t.done) { remove(t); removed = removed + 1; } } return removed; }
  • 38.
  • 39.
    one-way var n =0 label(n) button("Up", onclick={ n = n + 1; })
  • 40.
    two-way var n =0 inputNum(n) label(n)
  • 41.
  • 43.
    var amount =10 var percentage = 10 var total <- amount * (1 + percentage/100) inputNum(amount) inputNum(percentage) label(total)
  • 44.
  • 47.
    http://api.stackoverflow.com/0.8/questions?answers=true&body=true { "total": 746646, "page": 1, "pagesize": 30, "questions": [ { "tags": ["string", "assembly", "arm"], "answers": [], "question_id": 3092029, "owner": { "user_id": 320124, "user_type": "registered", "display_name": "SoulBeaver", "reputation": 195 }, "creation_date": 1277200629, "score": 0, "title": "ARM - Infinite Loop While Searching String", "body": "...", ... }, ... ] }
  • 48.
    { "total": 746646, "page": 1, "pagesize": 30, "questions": [ { "tags": ["string", "assembly", "arm"], "answers": [], "question_id": 3092029, "owner": { "user_id": 320124, "user_type": "registered", "display_name": "SoulBeaver", "reputation": 195 }, "creation_date": 1277200629, "score": 0, "title": "ARM - Infinite Loop While Searching String", "body": "...", ... }, ... ] } external type QuestionsResultSet { total : Num page : Num pagesize : Num questions : Array<QuestionResult> }
  • 49.
    { "tags": ["string", "assembly", "arm"], "answers": [], "question_id": 3092029, "owner": { "user_id": 320124, "user_type": "registered", "display_name": "SoulBeaver", "reputation": 195 }, "creation_date": 1277200629, "score": 0, "title": "ARM - Infinite Loop While Searching String", "body": "...", ... } external type QuestionResult { tags : Array<String> answers : Array<AnswerResult> owner : OwnerResult creation_date : Num ... }
  • 50.
    service StackOverflow { root = "http://api.stackoverflow.com/0.8" resource questions(answers : Bool, body : Bool) : QuestionsResultSet { uri = "/questions" method = "GET" encoding = "json" } ... }
  • 51.
    function fetchQuestions() { var res = StackOverflow.questions(answers=true, body=true); for(question : QuestionResult in res.questions) { mapQuestion(question); } }
  • 52.
    entity Question { questionId : Num title : String body : Text answers : Collection<Answer> (inverse: question) creationDate : DateTime owner : User } entity Answer { question : Question answerId : Num owner : User body : Text } entity User { userId : Num name : String reputation : Num }
  • 53.
    function mapQuestion(qr :QuestionResult) : Question { var q : Question = cachedQuestion(remote.question_id); if(q == null) { q = Question { questionId = qr.question_id, title = qr.title, body = qr.body, answers = mapAnswers(qr.answers), creationDate = DateTime.fromTimestamp(qr.creation_date), owner = mapUser(qr.owner) }; add(q); } return q; }
  • 55.
  • 56.
    mobl code parse check desugar generate code HTML/Javascript
  • 57.
    entity Task { name : String (searchable) done : Bool dueDate : DateTime } Javascript using persistence.js HTML5 ORM tasks.Task = persistence.define('tasks__Task', { 'name': 'TEXT', 'done': 'BOOL', 'dueDate': 'DATE' }); tasks.Task.textIndex('name');
  • 58.
    screen root() { header("Todo") ... } Javascript functions building DOM tasks.root = function(callback, screenCallback) { var root1018 = $("<div>"); mobl.header(ref("Todo"), function(node) { root1018.append(node); ... }); };
  • 59.
    function cleanDoneTasks() :Num { var removed = 0; for(t in Task.all()) { if(t.done) { remove(t); removed = removed + 1; } } return removed; } tasks.cleanDoneTasks = function() { var removed = 0; var results = Task.all(); for(var i = 0; i < results.length; i++) { var t = results[i]; if(t.done) { remove(t); removed = removed + 1; } } return removed; }
  • 60.
    function cleanDoneTasks() :Num { var removed = 0; for(t in Task.all()) { if(t.done) { remove(t); removed = removed + 1; } } return removed; } tasks.cleanDoneTasks = function() { var removed = 0; var results = Task.all(); for(var i = 0; i < results.length; i++) { var t = results[i]; if(t.done) { remove(t); removed = removed + 1; } } return removed; };
  • 61.
    tasks.cleanDoneTasks = function(){ var removed = 0; var results = Task.all(); for(var i = 0; i < results.length; i++) { var t = results[i]; if(t.done) { remove(t); removed = removed + 1; } } return removed; }; continuation-passing style transform tasks.cleanDoneTasks = function(callback) { var removed = 0; Task.all(function(results) { for(var i = 0; i < results.length; i++) { var t = results[i]; if(t.done) { remove(t); removed = removed + 1; } } callback(removed); }); };
  • 62.
  • 63.
    screen root() { var n = 8 label(n * n) button("Inc", onclick={ n = n + 1; }) }
  • 64.
    var n =8 var n = ref(8); Observable - set(value) - get() - addEventListener(eventType, callback)
  • 65.
    label(n * n) varnode565 = $("<span>"); node565.text(n.get() * n.get()); n.addEventListener("change", function() { node565.text(n.get() * n.get()); }); root.append(node565);
  • 66.
    button("Inc", onclick={ n = n + 1; }) var nodes566 = $("<span class='button'>"); node566.text("Inc"); node566.click(function() { n.set(n.get() + 1); }); root.append(node566);
  • 67.
    screen root() { var n = 8 label(n * n) button("Inc", onclick={ n = n + 1; }) }
  • 68.
  • 69.
    many mobile platforms HTML5/JS avoid AppStore approval
  • 70.
    statically-typed WebDSL-like language generates HTML/JS CPS transform/reactive programming
  • 71.