我的網誌已搬家至blog.miaout17.net,本網誌將不再更新

2009年7月23日 星期四

Haskell Monad : 以Brainfuck Interpreter為例 (上)

斷斷續續的看Haskell也有一段時間了,也拿它來寫過一些解數學題的小程式,但是關於Type System, Monad之類的東西一直沒有靜下心來好好研究。直到最近開始看Real World Haskell這本書,才對於Monad有了一些基本的領悟。不過沒動手寫過,總會覺得不是真正的了解這些東西。於是我就想要寫一個小小的application。

和之前一樣,選了Brainfuck Interpreter這個題目開刀。這也是我第一次寫Haskell application,coding手法上還很生疏。以下的例子分成三個版本。第一個版本是簡單的實作,第二個版本加入了State Monad。第三個版本是最完整的實作,加入了Monad Transformer, Unit Test並切分模組。

編程環境:GHC 6.10.3



首先,我試著完全不使用Monad,實作了第一個版本的Brainfuck Interpreter (實作一源碼)。先來看看資料結構:data BFData = BFData {
curr :: Word8,
left :: [Word8],
right :: [Word8]
} deriving(Show)

data BFState = BFState {
codeLeft :: [Char],
codeRight :: [Char],
bfData :: BFData
} deriving(Show)


BFData表示Brainfuck運行時的Memory,Word8是Haskell中的8-bit unsigned integer。Brainfuck在運行時,記憶體像是被單一指標指著的陣列,我使用兩個list及一個數值表示這樣的資料結構:curr表示目前的值,right表示指標位置右側的值,left表示指標位置左側的值。當指標向右移動的時候,將curr的值塞到left的前端,將right的head塞到curr中。眼尖的人或可發現:left中的值排放順序是相反的。這樣可以在常數時間內做完指標移動的動作。

BFState為整個Brainfuck interpreter的狀態。codeLeft和codeRight使用了和data相似的資料表示方式:未執行及已執行的code。BFState裡也包括了BFData的資料結構。

再來是函數的定義,版本一中盡量都是以pure function去操作資料。我們先略去函式的定義,觀察一些函式的型別

fwd :: BFData -> BFData -- 資料指標前進
back :: BFData -> BFData -- 資料指標後退
modifyValue :: Word8 -> BFData -> BFData -- 修改資料指標
output :: BFData -> Char -- 讀取資料指標
blockBack :: BFState -> BFState -- 遇到右括號 (branch on nonZero) 時回退

可以發現,底層大部份的操作都寫成pure function。將一個資料轉為令一個資料,no side-effect, no implicit state。

在這個版本中我刻意的不做兩件事:不使用Monad記住狀態,不使用function呼叫的轉移來表示程式進行的狀態轉移(Erlang idiom)。寫一寫我才理解到為什麼Monad是重要的。Haskell是一個Single-assignment,大部份變數immutable的語言 (如同Erlang) 。在這樣的語言中,要修改一個複雜的狀態常常是很麻煩的,會出現x' = f(x); x'' = g(x'); x''' = h(x'')之類的敘述。另外就是錯誤狀態的傳遞 (ex. Maybe Monad)。這些東西通常使用Imperative Language更容易描述。

* Update: 這段話我寫的不夠好。剛剛回去看Josh Ko部落格,他做了更精確的解釋。引述一下他的說法:「把全部的 states 都寫出來,就不會為了顧及 implicit states 而在推理轉換時絆手絆腳;但當 state 規模變大時,處理起來就不那麼順手了,此時 imperative programs 的專門語法反而比較方便。」

在下一個例子我們可以看到如何使用State Monad來打造Imperative的環境,但再更高階的地方仍保有Pure functional的性質。



接下來,我實作了第二個版本,這個版本使用了State Monad (實作二源碼) 。

節錄一部份的Code來討論好了:



我先簡化了一下資料結構,將code及data都放在BFState的資料結構中。為了實作方便,這個版本不吃standard input,而把output丟到一個字串當中(bfOutput)。這個偷吃步可以完全隔離IO,並簡單地直接使用State Monad。

再來,觀查fwdCode, backCode, fwdData, backData四個函式,其型別都是State BFState ()。State其實是Control.Monad.State提供的Type Constructor。State s a即是一個內部狀態為s,回傳值為a的State Monad。這些東西我就不細述了,有興趣的話請去參考文件吧。

stepBF函式負責將直譯器推進一個指令。看看
c <- currCode
d <- getData
fwdCode

是不是回到了我們熟悉的Imperative programming的寫法?fwdCode看似造成了side effect (雖然這個side effect是被封裝在monad內的) 。

進ghci觀查一下型别,我們可以發現一下有趣的事。

*Main> :t bfOutput . snd . execBF
bfOutput . snd . execBF :: [Char] -> [Char]

我們可以將這些函式串起來,將State Monad的型別消掉,得到一個顯然的pure function:給定一個brainfuck code的字串,得到一個輸出的字串。而Pure function對測試顯然是有利的,我們可以保證每次的運行結果都一樣,不會受外在環境影響。

這個版本的缺點是:沒有辦法綁定標準IO。如果可能,我們會希望BFState的狀態機與輸入輸出可以解除耦合,透過形別系統可以同時綁定在標準IO/模擬的IO上。BFState綁定在標準IO時當然是Impure的:程式運行會受外部輸入影響。當透過模擬的IO時,我們可以將整個BFState的運行化為Pure Function。下個範例會顯示如何做到這一點。



(註:前兩個版本的Brainfuck interpreter algorithm其實是錯的:左括號當值為零的時候忘了向右branch,只是跑hello, world的例子剛好會對,我也懶得修了。另外輸入的指令其實沒有實作)



接下來應該要講正題了,第三個範例:
  • Haskell Brainfuck Interpreter
  • Monad Transformer:以Control.Monad.State.StateT為例
  • 使用StateT切割IO,同時以IO及pure function形式執行brainfuck interpreter
  • 在Unit Test中以pure function運行brainfuck interpreter
不過進入正題前就打了一堆字,拖稿等下集吧 (不知道什麼時候有空寫 XD)。

先放source code:有稍微切一下module,應該沒有前兩個範例那麼亂
source code available at Github

2009年6月20日 星期六

Apache下以不同uid及pid執行不同VirtualHost

這篇的東西我沒有很確定是對的,筆記一下,有錯請指正喔。



考慮以下情境:
  • 在一台Linux主機上執行Apache伺服器
  • 主機上有兩個使用者:a及b
  • 使用Apache VirtualHost將a.gaaga.com導向/home/a/www,將b.gaaga.com導向/home/b/www
一般來說,如果使用apt, yum之類的套件管理程式自動安裝Apache的話,Apache會自動被設定在某一個系統帳號權限下執行(www-data, nobody...etc)。在這個地方會遇到一些小問題:Apache可能無法讀寫使用者的www目錄 (讀取的問題在預設權限755得情況不會發生,但一般我還是覺得將權限開成700比較有保障)。

網路上可以找到一些解法:
  • chmod 777 -R www (超危險暴力法,強力不推薦使用)
  • chgrp -R www-data www; chmod -R g+a www
  • etc....
但這些做法都還有安全性上的隱憂:www-data有權限讀寫任何用戶的www資料夾,也就是a其實可以寫一段php code盜取b的資料,或將b的資料殺掉。

這個問題我google了一下,有點抓不到正確得關鍵字,於是直接上irc freenode#TOSSUG發問,感謝snowian pct的回答,最後找到了這個東西:
apache2-mpm-itk (just mpm-itk for short) is an MPM (Multi-Processing Module) for the Apache web server. mpm-itk allows you to run each of your vhost under a separate uid and gid — in short, the scripts and configuration files for one vhost no longer have to be readable for all the other vhosts.
在debian下的設置很簡單:
# apt-get install libapache2-mpm-itk
# vi /etc/apache2/sites-available/a-site
加上
AssignUserID a a (分別是userid, groupid)
即可

(找方法找了很久,找到後花三分鐘就搞定了)




另外,看到2007年的mailing list上的問題:會跟mod_python相衝。不知道這個問題解決了沒,我可能會在上面跑Django

2009年5月30日 星期六

在Linux/Mac作業系統上運行Blogtrans

這篇文章說明如何在其他作業系統上運行Blogtrans。

目前Blogtrans只固定製作Windows版的執行檔。在其他平台上運行Blogtrans可能麻煩一些,以下也假設讀者對於這些作業系統的操作有一定的認知。

筆者無法允諾有空一一回復相關問題。筆者並非微軟教徒,但考慮Windows使用人數眾多,Blogtrans在Windows上應該算是經過相當的測試。如果使用上還是有問題,借個有灌Windows的電腦來用似乎是最穩定的選擇。

如果有人要幫忙做Linux和Mac的Build我也是很樂意啦....

環境設定

目前Blogtrans只相依於兩個東西:
  1. python 2.5
  2. wxPython-unicode
(舊版本曾經用過gdata,現在的Blogger Atom XML是硬寫出來的)

以ubuntu (debain) 為例:
sudo apt-get install python
sudo apt-get install python-wxaddons

我手邊沒有Mac (之前有Mac桌機但賣掉了) , 不過Mac印像中也是內建Python2.5,應該灌個wxPython-unicode就OK吧......

執行Blogtrans

然後去Github的Blogtrans頁面抓取程式源始碼,目前穩定的程式碼都放在master branch。如果會使用git的話可以用git抓,否則直接抓tarball下來解壓縮也可以。

之後進到shell,切換到blogtrans目錄,執行python blogtrans.py即可。

在Ubuntu上運行Blogtrans的截圖:



2009年5月22日 星期五

Blogtrans 更新 090522

今天赫然發現一件事,在上次的更新中我以為已經修正無名文章日期的問題了。結果發現修改的程式忘記commit,上次壓出來的版本日期還是有問題的。所以今天又壓了一個版本,只更新了無名日期這件事。

離無名關閉備份只剩四天。

有需要者請至Sourceforge抓取最新版本。

2009年5月11日 星期一

Blogtrans雜記

先聲明,筆者跟無名沒啥深仇大恨,也不在Web界工作。更不是其他BSP的打手,我與任何BSP沒有利益關係。

Blogtrans專案的起源是幫朋友(終於變成女友XD)搬Blog寫的一個console小程式,後來閒暇時補上UI放出來給想搬家的人使用。這也是我剛第一個嘗試用Python寫的中小型應用程式 (所以程式架構很亂,有人要trace的話別笑我 Orz)。

維護這隻程式算是吃力不討好的工作。無名的規格問題層出不窮:在XML檔案後面添加HTML碼的做法是一大經典,而且這是推出備份服務的後來才加上去的,我懷疑這是刻意讓企圖讀取無名備份檔的程式無法運作。另外在XML檔中可以出現不合法的XML字元,還有現在的備份檔不完全問題。 (Well, 我必需承認無名匯出檔根本是封閉格式,或許該慶幸他沒有用亂七八糟的binary format再加個密?) 另外也有些MT匯出檔的格式不依標準 。最麻煩的是上傳到Blogger,我花了許多時間在測試上傳Blogger備份檔上。




話說回來,無名從以前到現在的做法著實另人惱火,我相當不相信「關閉備份是為了專注在其他更好的服務」。

筆者在思考是不是該在無名關畢備份服務後,再寫另一個轉出無名的功能。要達到私密文章備份,這次可能就要靠HTTP認證,再從前台或後台撈HTML出來了。跟之前的作法比,可能這樣的做法更為麻煩。只要站方稍稍變動規格,撈文章的parser就可能失效。

我另一方面也希望國內其他BSP可以繼續做到這樣的功能 (撇開轉出Blogger部份不提的話,pixnet一直提供了比Blogtrans更穩定的轉出無名功能)。畢竟跟商業(又有幾位程設能力超強大神在)的BSP比,筆者一人利用空閒時間的力量是相當有限的。

2009年5月10日 星期日

無名小站備份檔問題

按,我家樓下常有外國人在開轟趴,半夜一點多大聲說話,吵到我關著窗子都聽的到。剛快睡著結果被吵起來寫blog,考慮下次再被吵要不要去暱名檢舉了....



言歸正傳,昨天收到一位網友來信,說Blogtrans無法匯入他的備份檔。他的網誌有九百多篇文章,但是從無名試著匯出好幾次備份檔,使用Blogtrans匯入時都不成功。使用Notepad++打開他的備份檔,檢視檔案尾端,大鳥眉頭一皺,發現案情並不單純 (對不起我老梗了):


因為沒有取得轉錄同意,雖然原文是公開的,應該在合理引用的範圍,我還是把文字消音了。

注意紅框的地方,一個E9的特殊字元中斷了備份檔。我試著手動修復備份檔,發現無名只匯出了七百多篇文章,留言全都不見了。

假設不是該網友在下載的過程中遇到特殊問題,那就是無名的備份功能真的有Bug,導致匯出時闕漏了文章和留言。

該位網友去信無名,客服的回應為
會幫他調整Blog,請他清除網路暫存檔試試看
(顯然沒用?)

或許無名是對的:關閉服務是因為他們沒有能力維護。

你還放心住在無名嗎?



(對不起我又老梗了)

2009年5月3日 星期日

Blogtrans版本更新:090503


遵守之前的承諾,在這個週末放出Blogtrans更新。這次的更新主要是針對這些問題:
  • 匯入無名小站時,文章時間是最後修改時間,而非發佈時間
    已修正
  • 無名小站無法匯入的一些問題
    主要問題是當文章中有不合法的XML字元時,無名仍會將這些字元匯出到XML檔中。
    已解決
  • 簡體中文電腦無法使用Blogtrans的問題
    1. 原本程式中有些地方很蠢的將字串先以CP950轉成Unicode,在簡體中文電腦就會產生錯誤,我全部改成Unicode了。
    2. 看到MT檔中,日期欄位是「DATE: 07/11/2004 07:39:17 下午」,出現中文字當然與MT規格不符,暴力的replace /上午/AM/ /下午/PM/
    已解決
  • 匯出至Blogger留言會不見的問題

    這個問題我懷疑是Blogger自己的問題,之前匯出留言到Blogger是沒有問題的。我做了這樣的實驗:從Blogger官方匯出有留言的文章,再匯入回去,留言竟然也有機率會不見。
    暫時提供一個比較暴力的解決方法:將所有留言合併到文章內文之中。

    選擇這個選項之後,所有留言會被合併到文章之中,當作文章的一部份處理。這樣做的壞處是留言會被視為文章的一部份,而無法在側邊欄中顯示。

    這個問題若是持續存在,我會在有空時做詳細的測試再回報給Blogger。

    目前無法完全解決,提供克難的替代方案。


    090504更新:感謝Asher網友,將留言選項調成「任何人都可以留言」似乎就沒問題了。我還沒時間做詳細的測試,暫時先告知使用者有這個方法可以嘗試。
目前最新版的是Build-090503,一樣請至sourceforge下載。有問題請回報,祝大家成功逃離丁丁星球。