4

我正在完成令人惊叹的“在 48 小时内为自己写一个计划”,并完成了核心任务并想扩展它,但遇到了问题。我想做的是使eval函数可用于运行时,但在将其存储到全局环境中时遇到问题。

运行时环境的类型:

type Env = IORef [(String, IORef LispVal)]

Haskelleval实现的类型为:

eval :: Env -> LispVal -> IOThrowsError LispVal

全局环境是一个类型的映射:

primitiveBindings :: IO Env

因为它包含执行 IO 的函数和纯函数。我的尝试是将运行时设置为部分应用全局环境eval的主机,如下所示:eval

baseFun :: [(String, [LispVal] -> IOThrowsError LispVal)]
baseFun = [("eval", unaryOp (eval (liftIO $ readIORef primitiveBindings)))]

在哪里unaryOp

unaryOp :: (LispVal -> ThrowsError LispVal) -> [LispVal] -> ThrowsError LispVal
unaryOp f [v] = f v

然后我想将元素添加到全局环境中,但出现以下编译错误:

Couldn't match expected type `IORef a'
       against inferred type `IO Env'
In the first argument of `readIORef', namely `primitiveBindings'
In the second argument of `($)', namely `readIORef primitiveBindings'
In the first argument of `eval', namely
    `(liftIO $ readIORef primitiveBindings)'

这种模式似乎readIORef env经常发生在代码中,所以不清楚为什么它在这里不起作用。我将非常感谢对我的错误的启发。作为参考,我的代码几乎与作为参考的原始教程的最终代码完全一样。

谢谢

4

4 回答 4

4

据我所知,primitiveBindings不是一个全局环境,而是一个动作,当执行该动作时,会使用已经设置的原始绑定生成一个新的映射。也许更好的名字是createPrimitiveBindings. 没有unsafePerformIOhack 就没有办法在 Haskell 中拥有适当的全局变量。

因此,您尝试添加的方式eval将使其评估环境中的所有内容,因为您正在重新运行该primitiveBindings操作。如果这确实是您想要的,我们可以轻松地处理类型错误:

baseFun :: [(String, [LispVal] -> IOThrowsError LispVal)]
baseFun = [("eval", evalInNewEnv)]
    where evalInNewEnv args = do
              env <- liftIO primitiveBindings
              unaryOp (eval env) args

如您所见,这里不需要任何内容readIORef​​,因为eval已经期望Envwhich 只是 . 的类型同义词IORef

但是,在我看来,您想eval在“全局”环境中工作,在这种情况下,您需要以某种方式将此环境传递给您,因为正如我所提到的,没有全局变量。例如,我们可以定义这样的东西,它会eval在其中创建一个新环境。

primitiveBindingsWithEval :: IO Env
primitiveBindingsWithEval = do
    env <- primitiveBindings    
    bindVars env [("eval", unaryOp (eval env))]

primitiveBindings然后,primitiveBindingsWithEval当您想要一个新的环境时,您可以替换现有的使用eval

于 2012-05-12T01:36:01.947 回答
3

您的问题(来自错误消息)是readIORef primitiveBindings.

由于readIORef :: IORef a -> IO aprimitiveBindings :: IO (IORef [(String, IORef LispVal)])类型不对齐(通知primitiveBindings包含在 中IO)。

试试primitiveBindings >>= readIORef

对于标题中的问题:您不能“从 IO 中获取元素”,因为IO世界是这样,并且 haskell 不会公开其实现,因此您只剩下Monad(bind and return)提供的接口. 如果需要,有人可以指导您更好地解释这一点。

于 2012-05-10T00:26:15.137 回答
2

假设: 的类型unaryOp实际上涉及IOThrowsError您编写的任何地方ThrowsError。然后,仅根据您在不知道域的情况下给出的类型,我可以猜测您的意思:

baseFun = [("eval", unaryOp (\lispVal -> liftIO primitiveBindings >>= flip eval lispVal))]

这看起来是否与您想要的相当接近?

于 2012-05-10T00:22:47.120 回答
2

IO有点随意地混合和纯函数。本质上,您有三个问题:

  1. 你想要unaryOp (eval (liftIO $ readIORef primitiveBindings))有类型[LispVal] -> IOThrowsError LispVal,但是unaryOp返回类型[LispVal] -> ThrowsError LispVal,和IOThrowsError不一样ThrowsError。有几种方法可以解决这个问题,但到目前为止最简单的是概括 的类型unaryOp

    unaryOp :: (a -> b) -> [a] -> b
    unaryOp f [v] = f v
    

    (当第二个参数不是单例列表时,您可能还应该提出更好的错误消息。)

    NowunaryOp做了它之前做的事情,但[LispVal] -> IOThrowsError LispVal如果你给它也可以返回LispVal -> IOThrowsError LispVal,这正是给eval 的东西。到目前为止,一切都很好。但:

  2. liftIO (readIORef primitiveBindings)不进行类型检查,因为primitiveBindings它不是一个IORef,它是一个在运行时产生IO的动作。因此,您可以执行读取with的操作,即 ie ,但实际上您不想这样做,因为您想将自身传递给,而不是其内容。所以你应该改用。但:IORefIORef=<<readIORef =<< primitiveBindingsIORefevalliftIO primitiveBindings

  3. eval接受 type 的参数Env,但liftIO primitiveBindingsis type IOThrowsError Env(实际上,它比这更通用,但这已经足够接近了)。因此,您需要先使用>>=do提取该值:

    evalOp val = do
      env <- liftIO primitiveBindings
      eval env val
    
    baseFun = [("eval", unaryOp evalOp)]
    

顺便说一句,为了解决问题标题的语气:在 Haskell 中,您不会从IO. 您所做的是编写函数,然后将其放入 IO以获取更多信息IO>>=(即do符号)并且return是“提升”函数以对IO值进行操作的常用技术。

于 2012-05-12T17:56:27.683 回答