跳转到内容

在 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 添加变量和赋值 → 
华夏公益教科书