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
As far as I can tell,
primitiveBindingsis 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 beencreatePrimitiveBindings. There is no way to have proper globals in Haskell withoutunsafePerformIOhacks.Therefore, the way you’re trying to add
evalwould make it evaluate everything in a new environment, since you’re re-running theprimitiveBindingsaction. We can easily take care of the type errors, if that is indeed what you intended:As you see, there is no need for any
readIORefhere, sinceevalalready expectsEnvwhich just a type synonym for anIORef.However, it sounds to me like you’d like
evalto 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 withevalin it.You can then replace existing uses of
primitiveBindingswithprimitiveBindingsWithEvalwhen you want a fresh environment withevalin it.