mobl:針對移動Web開發的DSL
簡介
現在,針對移動設備像智能手機和平板電腦的應用開發很流行。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語言有大量特性,目的都是為了提高移動開發者的生產率:
- 聲明式的用戶界面:使用mobl定義的用戶界面是以一種聲明式的方式指定的,并且會對應用程序狀態的改變做出響應,這與其它方法不同,在那些方法中狀態和視圖是嚴格分離的。
- 透明的數據持久性:mobl擁有使用實體定義來定義數據模型的語言結構。對實體對象做出的變更會自動實體化到數據庫中。對數據的查詢也是在實體級別完成的,而并不需要字符串嵌入式的SQL查詢,這和很多其它框架都是類似的。
- 原則上是靜態的,在需要的時候可以是動態的:mobl語言是靜態類型的語言,支持像錯誤突出顯示、引用解析和代碼自動完成等IDE特性。盡管如此,正如類型推論所說,在很多情況下我們不需要顯式指定類型。有些情況下,動態類型更方便,我們可以使用
Dynamic
類型,從而可以對Dynamic
變量的屬性和方法進行任意地訪問。 - 同步編寫腳本:我們可以在腳本語言中編寫應用程序的邏輯,那看起來和帶有類型的JavaScript非常相似。代碼是以同步的風格編寫的,并且會由編譯器使用持續傳遞的樣式轉換(continuation-passing style transform)將其自動轉換為異步的JavaScript代碼。
簡單的To-do列表
為了真正了解mobl是什么樣子的,我會在這個部分演示如何實現一個簡單的to-do列表管理器。
首先我們要在Eclipse中創建一個新的mobl項目,這樣會得到mobl項目的基本框架,其中有唯一的應用程序文件,我們把它命名為todo.mobl
:
import mobl::ui::generic
screen root() {
header("todo")
}
這個mobl模塊的第一行定義這是一個application模塊。mobl有三種不同類型的模塊:
- application模塊:通常每個項目有一個,這是應用程序的主要入口點。
- regular模塊:這是一個定義的庫,通常會由一個或者多個其他(應用程序)模塊導入。
- configuration模塊(config.mobl):定義應用程序的配置選項。
application和regular模塊包含任意數量的定義:用戶界面、數據模型、樣式、web服務接口和函數等等。
首先,讓我們來使用entity
定義來定義一個數據模型。實體的實例會持久化到移動設備本身的本地數據庫中。我們的to-do應用程序只需要唯一一個實體,名稱為Task
。對于每個task對象,我們都希望能夠記錄它的名稱,以及任務是否已經完成。
name : String (searchable)
done : Bool
}
對于每個屬性我們都會指定名稱和類型,并有選擇地加上一個或者多個注解。mobl支持兩種類型的注解:inverse
注解(定義反向的關系)和searchable
(在全文搜索中包含字段)。盡管這個應用程序不需要,但我們還是可以指定多個實體以及它們之間的關系,包括一對一、一對多和多對多的關系。
接下來,我們對root
屏幕進行改寫,從而顯示我們能夠勾選和取消勾選的任務列表。在mobl中,用戶界面是用screen
和control
定義的。通常screen
會通過組合大量control
來定義實體屏幕布局。這樣,control
會定義更小的用戶界面元素,像按鈕、標簽和表格等等。此外,屏幕或者控件也能夠定義本地狀態(使用變量),并使用控件結構來對集合進行迭代,從而根據條件顯示用戶界面的各個部分。
下面是我們的root
屏幕的最初定義(當程序運行時所顯示的第一個屏幕)。它使用了大量mobl::ui::generic
庫中的控件,包括header
(渲染出屏幕的標題)、group
(對一個或者多個item
進行分組)和checkBox
。
header("Tasks")
group {
list(t in Task.all()) {
item { checkBox(t.done, label=t.name) }
}
}
}
list
控件的結構與for-each循環類似:它會遍歷一個集合,對于集合中的每個項目,它都會進行渲染。checkBox
與兩個Task
對象屬性綁定:done
和name
。數據綁定會在應用程序狀態(例如本地變量或者實體屬性)和用戶界面之間創造出同步的關系。例如,當用戶選擇復選框或者取消對它的選擇時,t.done
的值就會據此更新。類似地,當應用程序的某些其他部分更新任務t
的name
屬性時,復選框也會自動更新它的標簽。這被叫做反應性編程(reactive programming),這也是根本的mobl特性之一:用戶界面會對應用程序狀態的改變做出反應。
最初時,數據庫是空的,從而在我們的列表中不會顯示任何任務。我們怎樣才能添加任務呢? 為了這個目的,我們定義了addTask
屏幕:
var newTask = Task()
header("Add") {
button("Done", onclick={
add(newTask);
screen return;
})
}
group {
item { textField(newTask.name, placeholder="Task name") }
}
}
使用var
結構我們可以創建嵌入在特定屏幕中的狀態。在這種情況下,我們定義了變量newTask
,并使用新建的Task
實體實例對其進行初始化。我們把textField
控件與newTasks
的name
屬性綁定。當用戶輸入完任務的名稱時,他就會點擊顯示在應用程序標題處的Done按鈕。按鈕擁有名為onclick
的參數,它的值是Callback
,當事件發生的時候,就會執行一段命令式的應用程序邏輯。在這個特定的情況下,發生了兩件事情:
- 向數據庫添加了
newTask
對象。這會在后臺對Task.all()
集合做出改變,使得新建的任務被自動添加到root
屏幕的list
中。 - 然后用戶會返回當初的屏幕(
screen return
和函數中的return
類似)。
然而,盡管我們定義了root
和addTask
屏幕,但沒有辦法從一個屏幕跳轉到另一個。我們需要做的就是向root
屏幕添加一個Add按鈕,它會帶我們跳轉到addTask
屏幕。因此,我們需要在root
中把對header
控件的調用調整為下面這樣:
button("Add", onclick={ addTask(); })
}
正如你所看到的,對屏幕的調用和對一般函數的調用類似,事實上,和函數一樣,屏幕也能夠返回值。
現在我們的應用程序的功能已經基本完備。我們還要添加最后一個特性:搜索。在我們的數據模型中,我們對Task
的name
屬性使用了(searchable)
注解。利用,我們就可以使用搜索來過濾任務列表(從而更快地找到我們想要查找的任務)。
我們需要把root
屏幕調整為下面這樣:
var phrase = ""
header("Tasks") {
button("Add", onclick={ addTask(); })
}
searchBox(phrase)
group {
list(t in Task.searchPrefix(phrase)) {
item { checkBox(t.done, label=t.name) }
}
}
}
我們向屏幕中添加了新的本地變量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列表只是個玩具一樣的例子,它只使用了一些簡單的控件,像group
、item
、button
和textField
。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
類型,它擁有對實體對象的集合進行過濾、排序和分頁的方法。我們可以像下面這樣來調用這些方法:
.order("date", false).limit(10);
很明顯,這些語法有些麻煩。因此,mobl為查詢添加了句法特性,讓我們可以把上面的查詢寫成這樣:
order by date desc limit 10;
這不僅更加簡潔,而且現在IDE可以檢查事實上Task
是否擁有done
和date
屬性,并且提供了恰當的代碼自動完成功能。
結論
mobl沒有在已存在的語言基礎之上構建框架,而是從頭開始,構建了一種外部DSL。這種方法有優點也有缺點。缺點在于用戶需要學習新語言、新庫以及新的工具。優點在于,選擇這種語言來進行設計,可以顯著減少開發者所要編寫的代碼。在mobl中,我們保持它的語法與語義與JavaScript類似,從而讓開發者覺得這種語言很熟悉。此外,mobl能夠集成到現有的Eclipse IDE和外圍工具中,提供在輸入時檢測錯誤、引用解析、代碼自動完成和保存時編譯等功能。
mobl還是一種很年輕的語言。第一次公開發布是在2011年1月。它的編譯器、工具和文檔還在逐步完善中。盡管如此,我們覺得它已經顯示出在移動領域使用DSL的潛力。
留言列表