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, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With