這兩天完善了我的jaskell語言的一個shell。

這個shell雖然是jaskell的,但是也可以作為一個交互式執行java代碼的解釋器。對于想快速地試試某個api比較有用。

相比于eclipse scrapebook,它的好處是更方便,而且,jaskell的一些函數式的特性讓你可以寫出更加簡潔的代碼。

下面舉幾個例子:

輕松玩Swing

打開shell,它顯示這樣:

Jaskell Shell version 
0.5
>
然后假設你要運行一下javax.swing.JOptionPane.showInputDialog()函數的話,你可以這樣寫:
> javax.swing.JOptionPane.showInputDialog["your age?"]
>
回車兩下(第一下,就是簡單折行,因為你可以接著寫下一行代碼,只有連續兩下折行,shell才認為你是要執行)
結果就會出現一個簡單的swing對話框。在對話框里面輸入年齡“13",回車,
shell里面就會顯示:13。


這里面,一點需要注意的,java方法調用不用圓括號,而是方括號。你可以把這個理解為一個reflection調用,傳遞的永遠都是一個數組。

下面,假設你想重復地使用JOptionPane這個類,使用showInputDialog, showConfirmDialog這類的方法,總這么寫javax.swing.JOptionPane也夠麻煩的。我們可以簡化它:
> dialog = javax.swing.JOptionPane
>
=> class javax.swing.JOptionPane
當你回車兩次后,shell在"=>"提示符后面自動顯示這個表達式的值:"class javax.swing.JOptionPane"。

下面我們可以重復使用dialog變量了。在這之前,我們可能想看看JOptionPane到底都支持什么靜態方法,我們可以用"?"來讓shell告訴我們
> ? dialog
這個"?"不是jaskell語言的一部分,而是shell的命令,所以不需要回車兩次。回車,shell就會把JOptionPane的所有方法都列出來。

然后假設我們選擇showMessageDialog,可以這樣寫:
> dialog.showMessageDialog[null"hello world"]
>
然后,一個"hello world"的對話框就彈了出來(看不見?找一找。它可能被藏在你的當前窗口后面了。)

更簡化一點,假設我要重復showMessageDialog若干遍,我可以這樣寫:
> say msg = dialog.showMessageDialog[null, msg]
>
=> say()
這個表達式的值是一個接受一個參數的,叫做say的函數。

下面你可以say很多東西啦:
> say "how are you?"
>

> say "java sucks!"
>
等等等等。



傻瓜多線程

好,看過了JOptionPane,我們來看看多線程。下面是用這個語言怎么啟動一個線程:
> Thread.new[(\_->System.out.println["hello world"]) `implements Runnable].start[]
>
hello world
這里面Thread.new大概不需要解釋。這里對構造函數的調用是Ruby風格的ClassName.new,而不是java的new ClassName。

構造函數也需要用一個list來傳遞參數。我們這里傳遞的是一個Runnable對象。
用來實現Runnable接口的是一個匿名函數,這個函數不管參數是什么,一旦調用,就println一下。

implements 是一個函數,它負責用一個函數來動態生成一個實現某接口的proxy出來。它前面的那個反向單引號表示把一個函數以中綴語法調用,所以 (somefunction `implements Runnable)等價于implements(somefunction, Runnable)。

"\"符號是lamda函數表示法。"->"符號前面的是函數參數,后面的是函數體。這里因為我們不使用這個參數,所以用"_"這個通配符。

最后,我們調用start[]方法來執行這個線程。


我們還可以用標準的"const"函數來讓代碼更簡短一點。"const x"語義上完全等價于"\_->x"。另外,我們也可以用java風格的new操作符函數來寫,jaskell對兩者都支持的:
> new Thread[const(System.out.println["hello world"]) `implements Runnable].start[]
>
hello world


然后,考慮到重用,我們可以這樣,先把System.out.println搞短一點,每次敲這么長太麻煩:
> println msg = System.out.println[msg]
>
=> println()

(實際上,println函數是系統已經缺省就定義好的了。你完全沒有必要自己定義println就可以直接用了。這里只是演示一下怎么自己定義函數)


然后,把那段啟動線程的代碼寫成函數:
> run task = Thread.new[const task `implements Runnable].start[]
>
=> run()

好了,下面我們可以任意啟動線程做事情了:
> run(println "hello world")
>
hello world

> run(println "pei!")
>
pei
!

> run (say "nice!")
>

最后一個say "nice!",如果不用單獨線程的話,運行后這個對話框將阻塞當前線程。現在用run來運行它,就不會阻塞了。


今天你fp了嗎?

最后再隨便看看jaskell作為函數式語言的本分所能做的一些事。

foreach函數:

> foreach [1,2,"hello"] println
>
這個函數把1,2,"hello"三個都打印一遍。

> foreach(list 1 100, println)
>
這個函數把數字1到100按順序打印一遍。

map函數:
> map (\x->Integer.parseInt[x]) ["1","2","3"]
>
=> [
1,2,3]
這個函數把一個字符串列表轉換成一個整數列表。


filter函數:


> filter(\x->x<10, list 1 100)
>
=> [
1,2,3,4,5,6,7,8,9]
這個代碼把1到100中所有小于10的整數都取出來。

filter函數的第一個參數是一個函數,這個函數對列表中的每一個元素都進行判斷,返回true或者false。

find函數:

> find(3, list 1 100)
>
=> 2
這個代碼在列表中尋找整數2,如果找到,返回找到的位置(0為第一個)


lookup函數:

> lookup(\x->x*x>x+50, list 1 100)
>
=> 7
這個代碼在列表中尋找第一個符合x*x>x+50的元素,找到就返回位置。

@函數:

上面我們知道找到的數字是在位置7,可以用@來得到位置7的值:

> list 1 100 @ 7
>
=> 8
好,這個數是8。


sum函數:

> sum(list 1 100)
>
=> 5050


注意,最難的來了!
fold函數:

> fold (*1 [1,2,3,4,5]
>
=> 120
這個fold函數以1為初始值,然后對每個列表元素,把它和當前值做乘法,用結果更新當前值,最后把計算結果返回。所以這段代碼實際上做的就是把從1到5乘起來。
(*)是一個乘法函數。

你也可以用自己寫的:
>  fold (\x y->x*y) 1 [1,2,3,4,5]
一樣的。


行了,差不多了。下載在:

http://dist.codehaus.org/yan/distributions/jaskell.zip

把jar文件都放到你的classpath里面,然后運行jfun.jaskell.shell.Shell類就行了。