如何初始化monad然后在Haskell中多次使用函数

时间:2011-04-19 05:49:15

标签: haskell monads interactive

大部分内容都来自提示示例。我想做的是用模块和导入等初始化解释器并以某种方式保持它。稍后(用户事件或其他),我希望能够调用具有该初始化状态的函数并多次解释表达式。所以在代码中的--split here位置,我希望在init中使用上面的代码,并在下面的代码中使用表达式并解释它的新函数。

module Main where
import Language.Haskell.Interpreter
import Test.SomeModule

main :: IO ()
main = do r <- runInterpreter testHint
          case r of
            Left err -> printInterpreterError err
            Right () -> putStrLn "Done."
          -- Right here I want to do something like the following
          -- but how do I do testInterpret thing so it uses the
          -- pre-initialized interpreter?
          case (testInterpret "expression one")
            Left err -> printInterpreterError err
            Right () -> putStrLn "Done."
          case (testInterpret "expression two")
            Left err -> printInterpreterError err
            Right () -> putStrLn "Done."

testHint :: Interpreter ()
testHint =
    do
      loadModules ["src/Test/SomeModule.hs"]
      setImportsQ [("Prelude", Nothing), ("Test.SomeModule", Just "SM")]
      say "loaded"
      -- Split here, so what I want is something like this though I know
      -- this doesn't make sense as is:
      -- testExpr = Interpreter () -> String -> Interpreter ()
      -- testExpr hintmonad expr = interpret expr
      let expr1 = "let p1o1 = SM.exported undefined; p1o2 = SM.exported undefined; in p1o1"
      say $ "e.g. typeOf " ++ expr1
      say =<< typeOf expr1


say :: String -> Interpreter ()
say = liftIO . putStrLn

printInterpreterError :: InterpreterError -> IO ()
printInterpreterError e = putStrLn $ "Ups... " ++ (show e)

2 个答案:

答案 0 :(得分:3)

如果我理解正确,您希望初始化编译器一次,并可能以交互方式运行多个查询。

主要有两种方法:

  • IO行动提升到您的Interpreter背景中(请参阅luqui的回答)。
  • 使用惰性IO将数据流走私进出您的程序。

我将描述第二种选择。


通过懒惰IO的魔力,您可以传递testHint一个惰性输入流,然后在testHint的主体中循环,以交互方式解释许多查询:

main = do
      ls <- getContents   -- a stream of future input
      r <- runInterpreter (testHint (lines input))
      case r of
         Left err -> printInterpreterError err
         Right () -> putStrLn "Done."

testHint input = do
      loadModules ["src/Test/SomeModule.hs"]
      setImportsQ [("Prelude", Nothing), ("Test.SomeModule", Just "SM")]
      say "loaded"

      -- loop over the stream of input, interpreting commands
      let go (":quit":es) = return ()
             (e:es)       = do say =<< typeOf e
                               go es
      go

go函数可以访问初始化解释器的已关闭环境,因此为它提供事件显然会在该初始化解释器的范围内运行。

另一种方法是从monad中提取解释器状态,但我不确定在GHC中是否可行(它要求GHC不要从根本上处于IO monad)。

答案 1 :(得分:3)

我无法理解你的问题。另外我对提示不是很熟悉。但我会试一试。

据我所知,Interpreter monad只是一个简单的状态包装器IO - 它只存在,所以你可以说例如。 setImportsQ [...]并且后续计算取决于该函数修改的“设置”。所以基本上你想要分享多个计算的monadic上下文。唯一的方法是留在monad中 - 在Interpreter中构建一个计算并运行一次。你不能拥有一个逃避并重用runInterpreter的“全局变量”。

幸运的是,InterpreterMonadIO的一个实例,这意味着您可以使用IO来交错Interpreter次计算和liftIO :: IO a -> Interpreter a次计算。基本上你是从内到外思考(对于Haskell的学习者来说是一个非常常见的错误)。不使用在IO中运行解释器中的代码的函数,而是使用Interpreter中运行代码IO(即liftIO)的函数。所以例如。

main = runInterpreter $ do
    testHint
    expr1 <- liftIO getLine
    r1 <- interpret "" expr1 
    case r1 of
        ...
    expr2 <- liftIO getLine
    r2 <- interpret "" expr2
    case r2 of
        ...

如果需要,您可以轻松地将后一个代码拉出到函数中,使用参考透明度的美感!直接把它拉出来。

runSession :: Interpreter ()
runSession = do
    expr1 <- liftIO getLine
    r1 <- interpret "" expr1
    case interpret expr1 of
        ...

main = runInterpreter $ do
    testHint
    runSession

这有意义吗?您的整个程序是一个Interpreter计算,只有在最后一刻才将其拉出IO

意味着您编写的每个函数都应该在Interpreter monad中。远离它!像往常一样,在程序的边缘使用Interpreter并保持核心纯粹的功能。Interpreter是新的IO)。