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

发布于 2024-11-02 05:13:50 字数 1648 浏览 2 评论 0原文

其中大部分直接来自提示示例。我想做的是用模块和导入等初始化解释器,并以某种方式保留它。稍后(用户事件或其他),我希望能够调用具有该初始化状态的函数并多次解释表达式。因此,在代码中的 --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)

Most of this is straight from the hint example. What I'd like to do is initialize the interpreter with modules and imports and such and keep it around somehow. Later on (user events, or whatever), I want to be able to call a function with that initialized state and interpret an expression many times. So at the --split here location in the code, I want to have the code above in init, and the code below that in a new function that takes an expression and interprets it.

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

薄情伤 2024-11-09 05:13:50

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

据我所知,Interpreter monad 只是一个围绕 IO 的简单状态包装器——它的存在只是为了让你可以说例如。 setImportsQ [...] 并让后续计算取决于该函数修改的“设置”。所以基本上你想共享多个计算的单子上下文。做到这一点的唯一方法是留在 monad 中——在 Interpreter 中构建单个计算并运行一次。您不能拥有转义并重用 runInterpreter 的“全局变量”。

幸运的是,InterpreterMonadIO 的一个实例,这意味着您可以使用 交错 IO 计算和 Interpreter 计算>liftIO::IO a->解释器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

这有道理吗?您的整个程序是一个解释器计算,只有在最后一刻才将其拉出到IO

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

I'm having trouble understanding your question. Also I am not very familiar with hint. But I'll give it a go.

As far as I can tell, the Interpreter monad is just a simple state wrapper around IO -- it only exists so that you can say eg. setImportsQ [...] and have subsequent computations depend on the "settings" that were modified by that function. So basically you want to share the monadic context of multiple computations. The only way to do that is by staying within the monad -- by building a single computation in Interpreter and running it once. You can't have a "global variable" that escapes and reuses runInterpreter.

Fortunately, Interpreter is an instance of MonadIO, which means you can interleave IO computations and Interpreter computations using liftIO :: IO a -> Interpreter a. Basically you are thinking inside-out (an extremely common mistake for learners of Haskell). Instead of using a function in IO that runs code in your interpreter, use a function in Interpreter that runs code in IO (namely liftIO). So eg.

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

And you can easily pull that latter code out into a function if you need to, using the beauty of referential transparency! Just pull it straight out.

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

main = runInterpreter $ do
    testHint
    runSession

Does that make sense? Your whole program is an Interpreter computation, and only at the last minute do you pull it out into IO.

(That does not mean that every function you write should be in the Interpreter monad. Far from it! As usual, use Interpreter around the edges of your program and keep the core purely functional. Interpreter is the new IO).

旧伤还要旧人安 2024-11-09 05:13:50

如果我理解正确,您希望初始化编译器一次,然后运行多个查询(可能是交互式的)。

有两种主要方法:

  • 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< /code> 函数可以访问初始化解释器的封闭环境,因此向其提供事件显然将在该一次初始化解释器的范围内运行。

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

If I understand correctly, you want to initialize the compiler once, and run multiple queries, possibly interactively.

There are two main approaches:

  • lift IO actions into your Interpreter context (see luqui's answer).
  • use lazy IO to smuggle a stream of data in and out of your program.

I'll describe the second option.


By the magic of lazy IO, you can pass testHint a lazy stream of input, then loop in the body of testHint, interpreting many queries interactively:

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

The go function has access to the closed-over environment of the initialized interpreter, so feeding it events will obviously run in the scope of that once-initialized interpreter.

An alternative method would be to extract the interpreter state from the monad, but I'm not sure that is possible in GHC (it would require GHC not to be in the IO monad fundamentally).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文