跳轉到內容

在 48 小時內編寫您自己的 Scheme/構建 REPL

來自 Wikibooks,開放書籍,開放世界
在 48 小時內編寫您自己的 Scheme
 ← 評估,第二部分 構建 REPL 新增變數和賦值 → 

到目前為止,我們已經滿足於從命令列評估單個表示式,列印結果並在之後退出。對於計算器來說,這很好,但這不是大多數人認為的“程式設計”。我們希望能夠定義新的函式和變數,並在以後引用它們。但在此之前,我們需要構建一個可以執行多個語句而不會退出程式的系統。

我們不會一次執行整個程式,而是要構建一個讀-評估-列印迴圈。這將從控制檯一次讀取一個表示式並互動地執行它們,在每個表示式之後列印結果。後面的表示式可以引用前面設定的變數(或者在下一節之後可以這樣做),讓您構建函式庫。

首先,我們需要匯入一些額外的 IO 函式。將以下內容新增到程式頂部

import System.IO

接下來,我們定義幾個輔助函式來簡化一些 IO 任務。我們想要一個列印字串並立即重新整理流的函式;否則,輸出可能會停留在輸出緩衝區中,使用者永遠看不到提示或結果。

flushStr :: String -> IO ()
flushStr str = putStr str >> hFlush stdout

然後,我們建立一個列印提示並讀取一行輸入的函式

readPrompt :: String -> IO String
readPrompt prompt = flushStr prompt >> getLine

將解析和評估字串並從 main 中捕獲錯誤的程式碼提取到它自己的函式中

evalString :: String -> IO String
evalString expr = return $ extractValue $ trapError (liftM show $ readExpr expr >>= eval)

並編寫一個評估字串並列印結果的函式

evalAndPrint :: String -> IO ()
evalAndPrint expr =  evalString expr >>= putStrLn

現在是將所有內容整合起來的時候了。我們希望讀取輸入,執行一個函式,並列印輸出,所有這些都在一個無限迴圈中。內建函式 interact幾乎做了我們想要的,但沒有迴圈。如果我們使用組合 sequence . repeat . interact,我們將得到一個無限迴圈,但我們將無法從中退出。所以我們需要自己滾動迴圈

until_ :: Monad m => (a -> Bool) -> m a -> (a -> m ()) -> m ()
until_ pred prompt action = do 
   result <- prompt
   if pred result 
      then return ()
      else action result >> until_ pred prompt action

名稱後面的下劃線是 Haskell 中用於重複但不返回值的單子函式的典型命名約定。until_ 接受一個表示何時停止的謂詞,一個在測試之前要執行的操作,以及一個返回操作的函式。後面兩個都針對任何單子進行了泛化,而不僅僅是 IO。這就是為什麼我們將它們的型別用型別變數 m 編寫,幷包含型別約束 Monad m =>

還要注意,我們可以像編寫遞迴函式一樣編寫遞迴操作。

現在我們已經擁有了所有機制,我們可以輕鬆地編寫 REPL

runRepl :: IO ()
runRepl = until_ (== "quit") (readPrompt "Lisp>>> ") evalAndPrint

並將我們的 main 函式更改為要麼執行單個表示式,要麼進入 REPL 並繼續評估表示式,直到我們輸入 quit

main :: IO ()
main = do args <- getArgs
          case length args of
               0 -> runRepl
               1 -> evalAndPrint $ args !! 0
               _ -> putStrLn "Program takes only 0 or 1 argument"

編譯並執行程式,然後試用一下

$ ghc -package parsec -fglasgow-exts -o lisp [../code/listing7.hs listing7.hs]
$ ./lisp
Lisp>>> (+ 2 3)
5
Lisp>>> (cons this '())
Unrecognized special form: this
Lisp>>> (cons 2 3)
(2 . 3)
Lisp>>> (cons 'this '())
(this)
Lisp>>> quit
$


在 48 小時內編寫您自己的 Scheme
 ← 評估,第二部分 構建 REPL 新增變數和賦值 → 
華夏公益教科書