Getting element out of IO in Haskell

haskell

Solution

As far as I can tell, `primitiveBindings` is not a global environment, but rather an action that, when performed, generates a fresh mapping with the primitive bindings already set up. Perhaps a better name for it would have been `createPrimitiveBindings`. There is no way to have proper globals in Haskell without `unsafePerformIO` hacks.

Therefore, the way you're trying to add `eval` would make it evaluate everything in a new environment, since you're re-running the `primitiveBindings` action. We can easily take care of the type errors, if that is indeed what you intended:

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

As you see, there is no need for any `readIORef` here, since `eval` already expects `Env` which just a type synonym for an `IORef`.

However, it sounds to me like you'd like `eval` to work in a "global" environment, in which case, you would need to have this environment passed to you somehow, since as I mentioned, there are no globals. For example, we can define something like this which would create a new environment with `eval` in it.

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

You can then replace existing uses of `primitiveBindings` with `primitiveBindingsWithEval` when you want a fresh environment with `eval` in it.

Problem

I am working through the amazing Write Yourself a Scheme in 48 Hours and have completed the core tasks and wanted to extend it but ran into problem. What I wanted to do is make `eval` function available to runtime, but have issue storing it into the global environment. The runtime environment is of type: ``` type Env = IORef [(String, IORef LispVal)] ``` The Haskell `eval` implementation is of type: ``` eval :: Env -> LispVal -> IOThrowsError LispVal ``` The global environment is a mapping of type: ``` primitiveBindings :: IO Env ``` as it contain functions performing IO mixed with pure functions. My attempt was to set the runtime `eval` to the host `eval` partially applied with global environment like this: ``` baseFun :: [(String, [LispVal] -> IOThrowsError LispVal)] baseFun = [("eval", unaryOp (eval (liftIO $ readIORef primitiveBindings)))] ``` Where `unaryOp` is: ``` unaryOp :: (LispVal -> ThrowsError LispVal) -> [LispVal] -> ThrowsError LispVal unaryOp f [v] = f v ``` I wanted to then add the elements into the global environment but I get a compile error of: ``` 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)' ``` It appear that this pattern of `readIORef env` happen often in the code, so it unclear why it's not working here. I would greatly appreciate enlightenment in my errors. For reference my code is almost exactly like the final code for the original tutorial as a reference. Thank you

Original source