一種語言 適合任何時候使用 — Haxe特性雜談
這個世界有很多的語言, 不同的語言適合不同的情境, Ruby因為很適合開發領域語言(DSL), 所以被大衛選擇用於開發Rails, JavaScript因為常用非同步模型和方便的回撥處理, 被Ryan Dah用於開發node, 而JAVA期望通過JVM一次編寫, 隨處執行. 各種語言因為不同的目標場景, 也使得語言本身的特性趨於目標場景, 決定了這些語言應該是什麼樣子, 比如C++因為效率的考慮和相容C的需求, 決定了C++之所以成為現在的C++.同時也有很多語言是為生成另外一種語言而生的, 最典型的就是CoffeeScript, 我知道我的語言要更加優美, 但是我也知道我無法徹底替代你, 這是這個世界中習慣和慣性帶來的悲哀, 所以, 不能替代你, 那我首先通過生成你來取代你. 這就是Coffee這樣語言的思路. 因為JavaScript是如此的不好用和難以簡單替代, 所以成為了這類語言的競技場.但是, Haxe就更加誇張了, 他也是一種通過生成其他語言而生的語言, 只不過, 他可以生成多達7種不同的語言.
Haxe簡單介紹
One language, everywhere.– Haxe’s slogan
Haxe可能是我學習的最冷門的語言了, 不過因為Haxe的特性, 他再冷門, 我在實際中用到他的可能性都應該比Lisp要強的多. 我可以這麼說, Haxe可能是世界上最有野心的語言, 並且它還真的部分做到了. 其實Haxe與我還真有些淵源, 因為我以前的leader出來創業, 最後選擇的就是Haxe. 並且對它的適用性和成熟程度都很認可, 甚至還向我推薦過.Haxe起源於flash社群, 號稱’multiplatform language’, 據說是一個喜歡OCaml但是又知道OCaml實在無法推廣的法國人Nicolas Cannasse開發的, 可以生成的語言如下: (具體的見
- JavaScript: 直接生成單個的.js檔案, 支援DOM API.
- Flash: 直接可以生成swf, 相容Flash Players 6到11.
- NekoVM: 可以直接編譯成NekoVM的位元組碼. NekoVM本來就挺冷門的.
- PHP: 很難想象哪個真的Phper會準備用Haxe替代掉自己的語言.
- C++: 生成原始碼並且包含Makefiles, 另外, 提到了一個名為NME的2D跨平臺遊戲庫. 簡單的看了一下, 從特性上看, 似乎是除了Cocos2D-X以外一個較為不錯的選擇, 而從一些介紹看, 這個庫本似乎是一個模仿Flash的庫, 因為使用了Haxe這個神奇的語言, 所以現在已經支援了從iOS到Android到Windows等眾多平臺了.
- C#和JAVA: 2.1以後開始支援, 但是還在實驗中, 官方稱在3.0時才算成熟.
因為能生成這麼多語言, 所以Haxe可以支援一些我們經常用到的框架和庫, 比如NodeJS, 比如直接生成PHP程式碼結合Apache做後臺, 比如生成C++程式碼以直接支援跨平臺的遊戲開發.以上的事實已經足夠讓我震驚了, 更逆天的是Haxe竟然還有自己的著色器語言解決方法, hxsl, 什麼叫征服世界, 那就是不僅要征服CPU上執行的語言, 還要征服GPU上執行的語言~~~總的來說, Haxe就是希望自己能幹任何事情, 而按它的理念, 它還真的有可能有一天能做到, 語言的世界很殘酷, 各種語言都有他最適合的情景, 沒有一種語言能統一世界, 但是假如真有的話, Haxe一定是最可能的那一個. 事實上, 我說Haxe的野心大並不是貶義, 這是事實上Nicolas腦海中存在的概念, 他自己就做過一個講座, 名字就叫Plans for World Domination(youtube視訊 — 請自備梯子), Haxe就是他佔領世界的計劃~~
在官方的Why user Haxe文件中提到了用Haxe的以下理由:
- 適用於客戶端, 服務端和桌面程式開發的ECMA風格的程式語言.
- 極為快速的編譯(這似乎不是理由, 因為不用Haxe都不用多出這道編譯流程, 更何況, 我實際感受是其實也沒有那麼快, 特別是編譯成C++的時候)
- 型別檢測的好處(事實上喜歡動態語言的人就是因為喜歡沒有型別檢測帶來的自由)
- 為你的目標平臺增加需要的語言特性.
- 開源
其中提到的乾貨不多, 其實我覺得支援編譯到多種語言, 所以支援多種平臺, 使得只需要學習一種語言就能適用於幾乎任何環境, 這就是最大並且足夠強大的理由了.
環境
Haxe 2.1, 我會通過生成C++程式碼和JavaScript程式碼來觀察結果. 其中JavaScript我用的不是HTML, 而是我可能稍微熟悉一點的Node, 所以還用了Hx-node這個haxe對nodejs的支援庫.另外, 有Haxe的REPL環境ihx, 和官方提供的在網頁上嘗試Haxe的http://try.haxe.org/
生成C++初體驗
從簡單的HelloWorld開始吧:HelloWorld.hx:
class HelloWorld { public static function main() { trace("Hello World!"); }}
從例子中能夠看到, 原來所謂的ECMA風格就是類似C#和JAVA的風格…我用來編譯成cpp的.hxml配置是cpp.hxml:
-main HelloWorld-cpp cpp-debug-D HXCPP_M64
通過haxe cpp.hxml
命令給我的生成結果是在cpp目錄下的一堆檔案, 其中cpp/src目錄下看到有100多行C++程式碼, 我甚至都不想現在就全都弄懂. 不過還好的是給我的執行檔案的確輸出了HelloWorld.
生成JavaScript With nodejs
首先需要通過haxelib install nodejs
命令來安裝haxe的nodejs庫. 然後通過以下配置(我命名為nodejs.hxml)編譯上例中的HelloWorld.hx生成想要的.js檔案.
-main HelloWorld-js helloworld.js-debug-lib nodejs
通過haxe nodejs.hxml
命令會生成兩個檔案, 一個是helloworld.js, 一個是helloworld.js.map(用於debug), 此時通過node helloworld.js
能看到輸出結果正常. 當然, 首先你得把node安裝好.
Haxe入門
變數和型別
支援以下型別:
- Integer(int): 整數型別, 不支援更進一步詳細的整數型別,比如short, long啥的, 類似動態語言的做法.
- Float: 浮點數, 也是一個浮點走天下.
- Boolean(Bool): 布林, 代表的是true和false.
- String: 字串, 不解釋.
- Void: 用於表示沒有型別和值.
- Dynamic: 類似C#中的dynamic型別, 意圖在靜態語言中加入動態的效果, 個人很欣賞的嘗試.
- Unknown: 類似javascript中的undefined.
另外, 還有null
關鍵字, 型別是Unknown<0>
.
定義一個變數的語法和UnityScript(Unity中的JavaScript)類似(或者說JScript), 我甚至懷疑javascript2將來就會是這個樣子.
var i : Int = 5;
不過還是有個區別, 那就是當同樣使用簡化的語法時:
var i = 5;
Haxe使用的是類似C++和C#的型別推導技術, 也就是說型別會在編譯器決定, 不影響執行效率, 而UnityScript則不一樣, 會影響執行效率. 就這點來說, Haxe是足夠先進的.同時, 作為現代語言, String的連線操作符是沒啥問題的.
class HelloWorld { public static function main() { var hello = "Hello,"; var world = "World!"; trace(hello + world); }}
自從發現javascript和C#的扭曲特性後, 我總是喜歡用1 + "1"
的操作來實驗一個語言, 很不幸的是, Haxe就是這樣中招的語言, 會無錯誤的輸出11.
函式
用的還是類似UnityScript的方式, 使用function
關鍵字, 同時用str:String
這樣的方式來表示引數型別, 通過把:Int
加到函式定義語句的結尾來表示返回值, 熟悉UnityScript的XD是無障礙了.
class HelloWorld { static function add(n1 : Int, n2 : Int) : Int { return n1 + n2; } public static function main() { trace( add(3,4) ); }}
操作符
沒有看到操作符過載的內容, 但是支援常見操作符, 包括>>>
這個javascript特有的和C系的++
, --
等.
流程控制
基本上該有的都有, while, do-while, for-in, switch. 不過也沒有啥亮點. 有些詭異的是去掉了常規的for, 即for(int i=0; i<length; ++i)
形式的for
語句. 不知道是為什麼. 倒是避免了javascript中for塊變數作用域的問題.稍微值得一提的是switch的自動break, case支援常量字串和用逗號分隔的多路匹配, 現在的語言一般都是從C那兒來的, C特別彆扭的switch也就成為了各大語言改進的物件, 有意思的是, 改進的效果都不一樣, Haxe就是這樣改的:
class HelloWorld { public static function main() { var choice : String = ""; while (true) { choice = Sys.stdin().readLine(); switch( choice ) { case "y", "Y": Sys.println("You surely want it!"); case "n", "N": Sys.println("you don't want it?!"); break; default: Sys.println("Not a valid choice."); } } }}
我沒有在case "y"
後面使用break, 但是不會執行到下面的case, 而事實上, 後面的那個break是用來從while死迴圈中退出的.上面的程式碼沒法在js下執行, 因為讀了命令列, 我們得使用nodejs中讀命令列的process模組, 上面的程式碼經過處理就成了下面這樣了:
import js.Node;class HelloWorld { public static function main() { Node.process.stdin.resume(); Node.process.stdin.setEncoding('utf8'); Node.process.stdin.on('data', function (chunk) { var choice : String = chunk.trim(); switch( choice ) { case "y", "Y": Node.console.log("You surely want it!"); case "n", "N": Node.console.log("you don't want it?!"); default: Node.console.log("Not a valid choice."); } }); }}
首先, 因為沒有了while
死迴圈, 所以可能得自己加個條件作為退出(這裡沒有), 其次, 為了使用js的模組, 可能得稍微費點周折.
- 在預先設為Haxe lib的地址
git clone https://github.com/cloudshift/hx-node.git nodejs
- 修改nodejs.hxml, 新增一個
-cp YOUR_PATH
. 比如我的地址是~haxe_libs
, 於是就是-cp ~/haxe_libs/nodejs
.
作用域
類作用域和函式作用域不用說了, 好訊息是Haxe有塊作用域, 也就是說, {}
範圍內定義的變數只在大括號範圍內有效, 這個很有很有用. 假如不能這樣的話, 需要用javascript扭曲的匿名函式hack, 太扭曲了.
Haxe特性
因為Haxe是如此的冷門, 所以明明本文是講Haxe特性的, 結果也拉入了一堆簡介. 好了, 以下是正題. 我選擇的特性列表直接來自於官方列出的列表Language Features, 沒有什麼比這個更有代表性了.
基於模版(class-based)的類和介面模型(類似JAVA)
Classic Object-Oriented class + interface model (similar to Java)
- 類的訪問限制只有public和private兩層, 相對一些語言(比如JAVA)還在C++經典的三許可權中去增加一個預設的package不同, Haxe甚至去掉了通常的private, 而把protected稱為private, 並且預設狀態就是private.