写点什么

mobl:针对移动Web开发的DSL

2011 年 5 月 06 日

简介

现在,针对移动设备——像智能手机和平板电脑——的应用开发很流行。Apple 公司的 AppStore(针对 iPhone、iPod 和 iPad)拥有超过 350,000 种应用,而 Android 的 marketplace 也快速追赶上来,现在已经拥有超过 200,000 种应用。然而,Android 和 iOS 并非是仅有的两种移动平台。 BlackBerry 也是有力的竞争者,此外还有 Nokia 。最近 Microsoft 发布了 Windows Phone 7 ,HP 也发布了新的 WebOS 设备。这样,对于用户来说有了多种选择,但对我们这些开发者来说却是个噩梦。我们应该针对哪种平台来开发应用程序呢?

在移动平台之间共享代码极度困难。每种平台都选择了自己的开发框架,还有自己的语言和 API。对于 iOS 开发,你需要使用 Objective-C 和 CocoaTouch API;对于 Android 开发,你要使用 Java 和 Android API;对于 Windows Phone 7,你需要使用.NET 和 Silverlight API。

然而,我们还是拥有一种解决方案:web 开发,特别是: Webkit 。我们会看到,所有主要的移动平台供应商(除了 Microsoft 之外)都在 Webkit 之上构建了他们的移动浏览器,而 Webkit 是当前最新的、速度最快的开源浏览器引擎。Webkit 支持多种移动应用所需要的 HTML5 特性,包括侦测触摸手势(轻击、强击和缩放)、定位 API(确定用户的位置),并且支持本地数据库(浏览器中的 SQLite 数据库,用于在本地缓存数据)。当前,在 Android、iOS、WebOS 以及 BlackBerry OS 的六款浏览器中,都对这些特性提供了本地支持。对于不包含基于 Webkit 的浏览器的设备,我们还可以使用 PhoneGap 。PhoneGap 让我们可以使用 web 技术(包括 HTML5)开发本地应用程序,并把应用程序包装成为本地应用程序,那样就可以分发给用户了(例如,通过平台的应用程序市场)。如果平台还没有内建的 WebKit 浏览器,那么 PhoneGap 就会为其提供。PhoneGap 应用程序可以在六种不同的移动平台上运行。

JavaScript 框架厂商注意到了这是个机会,于是就构建了多种能够在移动 web 上运行的框架。 jQuery Mobile Sencha Touch 都是比较典型的例子。这些框架很容易给人留下深刻的印象,因为对于当前的开发者来说,使用它们来为移动网络开发应用程序是一种不错的方式。然而,它们还都是基于 JavaScript、HTML 和 CSS 的,它们的目的都不是要开发应用程序,而是要开发包含超链接文档的网络应用。各种框架试图对这些语言进行调整,从而适合他们的新角色,但是这会引起你的思考,专门为开发移动应用程序 而设计的语言应该是什么样子的呢?

如果我们想要设计这样的一种语言,需要解决什么样的问题呢?

  • 首先要解决的就是工具的支持。从事企业级开发的开发者(比方说 Java 和.NET 的开发者)习惯使用像 Eclipse 之类的 IDE 特性,像在键入的时候就能够突出显示错误、代码自动完成、引用解析、代码大纲以及重构等等。JavaScript 和 HTML 在本质上就是动态的,这让它们很强大,但是也让工具厂商很难为其创建出 Eclipse 和 InteliJ 那种级别的 IDE。对于当前所有语言来说,良好的 IDE 支持都是前提条件。

  • 第二个要解决的问题是简洁。例如,用户界面框架经常会包含大量类似的代码,它们的作用就是把数据从数据库中复制到用户界面,或者把界面上的数据复制回数据库。我们的新语言应该减少开发者所需要编写的样板化代码。

  • 第三个问题是 JavaScript 的异步编程模型。在浏览器中,JavaScript 是单线程的,开发者需要使用回调机制来执行数据库查询之类耗费资源的操作,比方说,我们不会编写像下面这样的同步代码: ```

    var results = tx.executeQuery(“SELECT * FROM User”);
    for(var i = 0; i < results.length; i++) {

    }

复制代码
我们需要使用回调函数来编写代码: ```
tx.executeQuery("SELECT * FROM User", function(results) {
for(var i = 0; i < results.length; i++) {
...
});

当在大型应用程序中使用回调函数,就会导致编写出很难阅读的意大利面式代码。我们的新语言应该支持对于开发者更友好的异步编程模型

我们的研究小组(软件工程研究小组,位于荷兰代尔夫特理工大学)专注于编程的转换与实现。当前,我们主要专注于领域特定语言。对于当前移动领域的开发,我们觉得有一个很好的机会,可以为移动web 开发出一种领域特定语言。我们自问:如果从头开始的话,一门专注于开发移动web 应用程序的语言,应该是什么样子的呢? 结论就是 mobl

mobl:从 10,000 英尺高处俯瞰

mobl 是一种文本式的、静态类型、编译的语言,主要是通过它的 Eclipse 插件应用。这个插件提供了语法高亮显示、内嵌的错误突出显示、引用解析以及代码自动完成。mobl 编译器(集成在 IDE 中)会在每次保存的时候把 mobl 模块编译成 HTML、JavaScript 和 CSS 的组合。mobl 应用程序不依赖于任何特定的服务端技术,而只会处理应用程序的客户端部分。我们可以使用 AJAX 的方式调用(JSON)web 服务。

mobl 语言有大量特性,目的都是为了提高移动开发者的生产率:

  1. 声明式的用户界面:使用 mobl 定义的用户界面是以一种声明式的方式指定的,并且会对应用程序状态的改变做出响应,这与其它方法不同,在那些方法中状态和视图是严格分离的。
  2. 透明的数据持久性:mobl 拥有使用实体定义来定义数据模型的语言结构。对实体对象做出的变更会自动实体化到数据库中。对数据的查询也是在实体级别完成的,而并不需要字符串嵌入式的 SQL 查询,这和很多其它框架都是类似的。
  3. 原则上是静态的,在需要的时候可以是动态的:mobl 语言是静态类型的语言,支持像错误突出显示、引用解析和代码自动完成等 IDE 特性。尽管如此,正如类型推论所说,在很多情况下我们不需要显式指定类型。有些情况下,动态类型更方便,我们可以使用Dynamic类型,从而可以对Dynamic变量的属性和方法进行任意地访问。
  4. 同步编写脚本:我们可以在脚本语言中编写应用程序的逻辑,那看起来和带有类型的 JavaScript 非常相似。代码是以同步的风格编写的,并且会由编译器使用持续传递的样式转换(continuation-passing style transform)将其自动转换为异步的 JavaScript 代码。

简单的 To-do 列表

为了真正了解 mobl 是什么样子的,我会在这个部分演示如何实现一个简单的 to-do 列表管理器。

首先我们要在 Eclipse 中创建一个新的 mobl 项目,这样会得到 mobl 项目的基本框架,其中有唯一的应用程序文件,我们把它命名为todo.mobl

复制代码
application todo
import mobl::ui::generic
screen root() {
header("todo")
}

这个 mobl 模块的第一行定义这是一个application模块。mobl 有三种不同类型的模块:

  1. application 模块:通常每个项目有一个,这是应用程序的主要入口点。
  2. regular 模块:这是一个定义的库,通常会由一个或者多个其他(应用程序)模块导入。
  3. configuration 模块(config.mobl):定义应用程序的配置选项。

application 和 regular 模块包含任意数量的定义:用户界面、数据模型、样式、web 服务接口和函数等等。

首先,让我们来使用entity定义来定义一个数据模型。实体的实例会持久化到移动设备本身的本地数据库中。我们的 to-do 应用程序只需要唯一一个实体,名称为Task。对于每个 task 对象,我们都希望能够记录它的名称,以及任务是否已经完成。

复制代码
entity Task {
name : String (searchable)
done : Bool
}

对于每个属性我们都会指定名称和类型,并有选择地加上一个或者多个注解。mobl 支持两种类型的注解:inverse注解(定义反向的关系)和searchable(在全文搜索中包含字段)。尽管这个应用程序不需要,但我们还是可以指定多个实体以及它们之间的关系,包括一对一、一对多和多对多的关系。

接下来,我们对root屏幕进行改写,从而显示我们能够勾选和取消勾选的任务列表。在 mobl 中,用户界面是用screencontrol定义的。通常screen会通过组合大量control来定义实体屏幕布局。这样,control会定义更小的用户界面元素,像按钮、标签和表格等等。此外,屏幕或者控件也能够定义本地状态(使用变量),并使用控件结构来对集合进行迭代,从而根据条件显示用户界面的各个部分。

下面是我们的root屏幕的最初定义(当程序运行时所显示的第一个屏幕)。它使用了大量mobl::ui::generic库中的控件,包括header(渲染出屏幕的标题)、group(对一个或者多个item进行分组)和checkBox

复制代码
screen root() {<br></br>
  header("Tasks")<br></br>
  group { <br></br>
    list(t in Task.all()) { <br></br>
      item { checkBox(t.done, label=t.name) }<br></br>
    }<br></br>
  }<br></br>
}

list控件的结构与 for-each 循环类似:它会遍历一个集合,对于集合中的每个项目,它都会进行渲染。checkBox与两个Task对象属性绑定:donename。数据绑定会在应用程序状态(例如本地变量或者实体属性)和用户界面之间创造出同步的关系。例如,当用户选择复选框或者取消对它的选择时,t.done的值就会据此更新。类似地,当应用程序的某些其他部分更新任务tname属性时,复选框也会自动更新它的标签。这被叫做反应性编程(reactive programming),这也是根本的 mobl 特性之一——用户界面会对应用程序状态的改变做出反应

最初时,数据库是空的,从而在我们的列表中不会显示任何任务。我们怎样才能添加任务呢? 为了这个目的,我们定义了addTask屏幕:

复制代码
screen addTask() {<br></br>
  var newTask = Task()<br></br>
  header("Add") {<br></br>
    button("Done", onclick={<br></br>
      add(newTask);<br></br>
      screen return;<br></br>
    })<br></br>
   } <br></br>
   group {<br></br>
     item { textField(newTask.name, placeholder="Task name") }<br></br>
   }<br></br>
}

使用 var结构我们可以创建嵌入在特定屏幕中的状态。在这种情况下,我们定义了变量newTask,并使用新建的Task实体实例对其进行初始化。我们把textField控件与newTasksname属性绑定。当用户输入完任务的名称时,他就会点击显示在应用程序标题处的“Done”按钮。按钮拥有名为onclick的参数,它的值是Callback,当事件发生的时候,就会执行一段命令式的应用程序逻辑。在这个特定的情况下,发生了两件事情:

  1. 向数据库添加了newTask对象。这会在后台对Task.all()集合做出改变,使得新建的任务被自动添加到root屏幕的list中。
  2. 然后用户会返回当初的屏幕(screen return和函数中的return类似)。

然而,尽管我们定义了rootaddTask屏幕,但没有办法从一个屏幕跳转到另一个。我们需要做的就是向root屏幕添加一个“Add”按钮,它会带我们跳转到addTask屏幕。因此,我们需要在root中把对header控件的调用调整为下面这样:

复制代码
header("Tasks") {
button("Add", onclick={ addTask(); })
}

正如你所看到的,对屏幕的调用和对一般函数的调用类似,事实上,和函数一样,屏幕也能够返回值。

现在我们的应用程序的功能已经基本完备。我们还要添加最后一个特性:搜索。在我们的数据模型中,我们对Taskname属性使用了(searchable)注解。利用,我们就可以使用搜索来过滤任务列表(从而更快地找到我们想要查找的任务)。

我们需要把root屏幕调整为下面这样:

复制代码
screen root(){<br></br>
  var phrase = ""<br></br>
  header("Tasks") { <br></br>
    button("Add", onclick={ addTask(); }) <br></br>
  } <br></br>
  searchBox(phrase) <br></br>
  group { <br></br>
    list(t in Task.searchPrefix(phrase)) { <br></br>
      item { checkBox(t.done, label=t.name) } <br></br>
    } <br></br>
  } <br></br>
}

我们向屏幕中添加了新的本地变量phrase,可以在其中存放查询的短语。我们使用searchBox,并将其与phrase绑定。然后,我们并没有遍历list中的Task.all(),而是遍历了搜索集合Task.searchPrefix(phrase)。在运行的时候,当我们在搜索查询中输入内容时,搜索结果的列表就会更新。此时用户界面会再一次根据应用程序的状态(这种情况下是phrase变量)自动调整。

现在我们已经完成了包含搜索功能的基本 to-do 列表应用程序的构建工作,接下来可以部署了。当我们保存 mobl 模块的时候,同时也会把它们编译成 JavaScript、HTML 和 CSS,这些文件位于 Eclipse 项目的www/目录下。我们可以把这些生成的文件部署到任意一个能够为静态文件提供服务的 web 服务器上(例如:Apache、IIS 或者 Tomcat),mobl 完全不需要后端程序。

To-do 列表之外

当然,to-do 列表只是个玩具一样的例子,它只使用了一些简单的控件,像groupitembuttontextField。mobl 的标准库还提供了一些高级的控件,像标签组、主从视图、上下文菜单以及可扩展的列表等等。

除了定义用户界面、数据模型以及应用程序逻辑的语言结构之外,mobl 还拥有以下结构:

  • 定义样式,在此它使用一种与 CSS 非常类似的语言。
  • 对 Web 服务的访问。从服务器拉入数据并缓存在本地。在将来,mobl 还会支持透明的数据同步。

想要了解这些特性,你可以查看 mobl 站点上的教程

使用 DSL 还是不使用 DSL

我们可以把 mobl 描述为一种领域特定语言(DSL),也就是一种针对特定应用程序领域的语言。在传统上,DSL 的领域很有限。例如,HTML 是一种定义结构化 web 页面的 DSL。SQL 是用来解释数据库查询的 DSL。移动应用程序的领域比上述要大得多。事实上,它非常大,以至于需要大量你通常只能在一般目的的语言或者 GPL——像 Java、Python 和 Ruby——中才能够找到的特性。这些典型的 GPL 特性包括面向对象编程、if 指令和 for 循环等等。既然 mobl 拥有 GPL 特性,那么它还是一种 DSL 吗?

我们觉得是,因为它拥有语言结构,这些结构都特别地适合数据驱动的移动 web 应用程序领域。例如,如果我们使用一种带有样式支持的一般目的语言,编写的是用于处理科学计算的程序,那么就不太合理了。而把screen结构应用于服务端计算也不是很合理。像实体、屏幕、控件、样式以及 web 服务等语言特性并不针对一般目的——它们都是针对特定领域的。

mobl 不仅仅是针对移动开发的 DSL。类似的还包括 Applause 和由此衍生的 Applitude 。然而,这些 DSL 的灵活性都有限。它们都拥有一系列的内建控件、内建函数,一旦这些都无法满足你的需求,你就需要重新使用 Objective-C 或者 Java 来编码了。mobl 的目标就是,既要灵活,又要具备较强的表达能力。

为了达到这个目的,我们让这门语言尽可能小,并且通过使用 mobl 本身编写库来增加功能。例如,我们用来构建 to-do 列表应用程序的控件都没有内建在语言之中。相反,它们都是从 mobl 库导入的,而这个库本身又是使用 mobl 定义的。在最低层级上,对控件的实现使用了低级的 HTML 标签和 CSS 样式。一般用户只会使用高级别的概念控件,像标题或者按钮等等,而专家级的开发者能够通过调整库中的底层 HTML 代码来精确地设计按钮的显示样式。

mobl 除了能够在库中定义控件之外,它还拥有暴露了大量 HTML5 API 的库,包括集合定位、使用 Canvas 进行 2D 绘图以及 WebSockets 等等。这些库只是封装了已存在的 JavaScript API,而 mobl API 使用 mobl 的本地接口,这使得它能够调用“本地”JavaScript 代码。这样,mobl 就可以通过库机制进行扩展,这让用户可以扩展平台,而不需要扩展语言和编译器本身。

在特定的情况下,mobl 会为特定的库添加句法的特性。例如,对于查询,mobl 暴露了Collection<t></t>类型,它拥有对实体对象的集合进行过滤、排序和分页的方法。我们可以像下面这样来调用这些方法:

复制代码
var doneTasks = Task.all().filter("done", "=", true)
.order("date", false).limit(10);

很明显,这些语法有些麻烦。因此,mobl 为查询添加了句法特性,让我们可以把上面的查询写成这样:

复制代码
var doneTasks = Task.all() where done == true
order by date desc limit 10;

这不仅更加简洁,而且现在 IDE 可以检查事实上Task是否拥有donedate属性,并且提供了恰当的代码自动完成功能。

结论

mobl 没有在已存在的语言基础之上构建框架,而是从头开始,构建了一种外部 DSL。这种方法有优点也有缺点。缺点在于用户需要学习新语言、新库以及新的工具。优点在于,选择这种语言来进行设计,可以显著减少开发者所要编写的代码。在 mobl 中,我们保持它的语法与语义与 JavaScript 类似,从而让开发者觉得这种语言很熟悉。此外,mobl 能够集成到现有的 Eclipse IDE 和外围工具中,提供在输入时检测错误、引用解析、代码自动完成和保存时编译等功能。

mobl 还是一种很年轻的语言。第一次公开发布是在 2011 年 1 月。它的编译器、工具和文档还在逐步完善中。尽管如此,我们觉得它已经显示出在移动领域使用 DSL 的潜力。

关于作者

Zef Hemel 现在是荷兰代尔夫特理工大学的博士生,他在那里研究对领域特定语言的设计和实现。他对 web 技术格外感兴趣。之前他曾经开发过 WebDSL PIL 。他经常在博客上发表博文,并在 twitter 账号上发推。

查看英文原文: mobl: a DSL for Mobile Web Development


给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011 年 5 月 06 日 00:003355
用户头像

发布了 340 篇内容, 共 111.3 次阅读, 收获喜欢 2 次。

关注

评论

发布
暂无评论
发现更多内容

第五周感想

数字

架构师训练营 - 总结 5

进击的炮灰

架构师0期05周作业

喵呜的小哥哥

架构师训练营第五周作业

张锐

week5作业

架构师训练营-第五周-作业

狂奔嘀兔纸

极客大学架构师训练营

Week05总结

张磊

【架构师训练营 - week5 -2】总结

早睡早起

本周总结

Geek_zhangjian

Week5

架构师0期05周总结

喵呜的小哥哥

《架构师训练营》第五周总结

「架构师训练营」作业:第 5 周 一致性 hash 算法

Amy

极客大学架构师训练营 作业 一致性hash 一致性Hash算法 第五周

架构师训练营 - 第五周 - 作业

Anrika

极客大学架构师训练营

架构师训练营-week5-学习总结

晓-Michelle

极客大学架构师训练营

第五周作业

qqq

极客大学架构师训练营

事件驱动架构(W5总结)

麻辣

第五周总结--一致性hash

互金从业者X

第五周总结

qqq

技术选型能力反映了架构师的综合水平(第 5 周学习总结)

李德政

极客大学架构师训练营

一致性哈希算法JAVA版简单验证

L001

一致性hash算法的实现

互金从业者X

架构师训练营-第五周-课程总结

狂奔嘀兔纸

极客大学架构师训练营

架构师训练营 -W5-20200708-学习总结

丁亚宁

架构师训练营 第五周 作业

一雄

极客大学架构师训练营 作业 第五周

【架构师训练营】第 5周作业

花生无翼

极客大学架构师训练营

第05周 技术选型-01 命题作业

Jaye

架构师训练营-作业3

A Matt

一致性哈希算法与实现

俊俊哥

缓存 算法 一致性哈希

架构师训练营第五周感悟

张锐

架构师训练营 - 作业 - 第五周

心在飞

极客大学架构师训练营

mobl:针对移动Web开发的DSL-InfoQ