2012-05-09 31 views
4

我正在通过令人惊叹的Write Yourself a Scheme in 48 Hours工作,并完成了核心任务并想扩展它,但遇到了问题。我想要做的是让eval函数可用于运行时,但有问题将其存储到全局环境中。在Haskell中获取元素输出IO

的运行时环境的类型是:

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

Haskell的eval实现类型:

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这种模式发生Ø在代码中,因此它不清楚为什么它不在这里工作。我非常感谢我的错误启发。作为参考,我的代码几乎完全像final code for the original tutorial作为参考。

谢谢

+1

如果你澄清了'ThrowsError'和'IOThrowsError'之间的关系,这将会很有用。我去看看它,看起来像'ThrowsError'是''LispError''和'IOThrowsError'''ErrorT LispError IO',粗略地说。 –

回答

4

据我所知,primitiveBindings一个全球性的环境,而是一个行动是,执行时,产生与已建立的原始绑定一个新的映射。也许更好的名字应该是createPrimitiveBindings。在没有unsafePerformIO黑客的情况下,没有办法在Haskell中拥有适当的全局变量。

因此,这样你想添加eval将使它在环境评估的一切,因为你重新运行primitiveBindings行动。我们可以很容易地将错误类型的照顾,如果这确实是你想要的结果:

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

正如你看到的,也没有必要在这里任何readIORef,因为eval已经预计Env这只是一种类型的代名词的IORef

但是,听起来我想你喜欢eval在“全局”环境中工作,在这种情况下,您需要将该环境以某种方式传递给您,因为正如我所提到的,没有全局。例如,我们可以定义类似这样的内容,它将创建一个新的环境,其中包含eval

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

您可以primitiveBindingsWithEval当你想用它eval一个清新的环境,然后替换现有primitiveBindings用途。

+2

由于'unaryOp'的返回类型不支持'IO',所以你修正的类型错误并不完全正确。在你可以像这样使用它之前,你需要概括一下'unaryOp'的类型。 –

+0

这个答案和benmachine后续行动是什么使我的大脑点击!作为不错的奖金,这个修复问题我不知道我有过。我真的很感谢帮助。谢谢。 –

2

假设:的unaryOp类型实际上涉及IOThrowsError到处都写ThrowsError。然后,只是你不知道的领域已经放弃的类型去,我可以做一个关于你的意思猜测:

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

确实,看起来就像是相当接近的,你想要什么?

+1

'IOThrowsError'和'ThrowsError'是不同的类型。无可否认'unaryOp'可以变得更多态,在这种情况下,这并不重要。 –

3

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

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

尝试primitiveBindings >>= readIORef

对于标题中的问题:由于IO是世界,而且haskell不公开它的实现,所以你只剩下由接口提供的接口Monad(绑定并返回)。如果需要,有人可以指导您更好地解释这一点。

+0

更改为'baseFun = [(“eval”,unaryOp(eval(primitiveBindings >> = readIORef)))]'给出与现在在报告中相同的类型错误“在第一个参数(>> =)'” 。也许我错过了别的东西? –

2

你在混合IO和纯粹的功能有点随意。从本质上讲,你有三个问题:

  1. 你想unaryOp (eval (liftIO $ readIORef primitiveBindings))有型[LispVal] -> IOThrowsError LispVal,但unaryOp返回一个类型[LispVal] -> ThrowsError LispValIOThrowsError是不一样的ThrowsError。有几种方法来解决这个问题,但截至目前为止最简单的是推广的unaryOp类型:

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

    (你或许应该也想出了一个更好的错误消息,当第二个参数不是单列表)。

    现在unaryOp做什么之前做了,但是还可以返回[LispVal] -> IOThrowsError LispVal如果你给它LispVal -> IOThrowsError LispVal,这正是eval给它。到现在为止还挺好。但是:

  2. liftIO (readIORef primitiveBindings)不进行类型检查,因为primitiveBindings不是IORef,这是一个IO的行动,产生IORef时运行。因此,您可以使用=<<(即readIORef =<< primitiveBindings)执行读取IORef的操作,但实际上您不需要要求来执行此操作,因为您要将IORef本身传递给eval,而不是其内容。所以你应该使用liftIO primitiveBindings来代替。但是:

  3. eval需要Env类型的参数,但liftIO primitiveBindings是类型IOThrowsError Env(实际上,它比这更普遍,但是这足够接近)。所以你需要使用>>=do首先提取值:

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

顺便说一句,解决问题的标题的语气:在Haskell,你没有得到的东西出来的IO。你所做的是你写的功能,然后你把纳入IO得到更多IO了。 >>=(即do表示法)和return是“提升”功能在IO值上操作的常用技术。

+1

这其实只是其他答案中突出显示的问题的一种组合,但没有人一次就清楚地解释了他们三个:P –