Getting an element from IO in Haskell

I am working on an amazing one. Write yourself a scheme after 48 hours and complete the basic tasks and want to expand it, but ran into a problem. What I wanted to do was make the eval function available at runtime, but you have a problem with storing it in the global environment.

The runtime has the 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 the type:

 primitiveBindings :: IO Env 

since it contains functions that perform IO mixed with pure functions. My attempt was to set the eval runtime to the eval host, partially applied with a global environment like this:

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

Where unaryOp :

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

I would like to add elements to the global environment, but I get a compilation error:

 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 seems that this readIORef env pattern is often found in code, so it is not clear why it does not work here. I am very grateful for the enlightenment in my mistakes. For reference, my code is almost exactly like the final code of the source tutorial as a reference.

thanks

+4
source share
4 answers

As far as I can tell, primitiveBindings not a global environment, but rather an action that, when executed, creates a new mapping with already established primitive bindings. Perhaps the best name for it would be createPrimitiveBindings . It is not possible to have the correct global variables in Haskell without unsafePerformIO hacks.

Therefore, the way you are trying to add eval will cause it to evaluate everything in the new environment, since you are rerunning the primitiveBindings action. We can easily take care of type errors if this is really what you intended:

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

As you can see, there is no need for readIORef , as eval already expects Env , which is just a type synonym for IORef .

However, it sounds to me as if you want eval work in a β€œglobal” environment, in which case you would need to somehow pass this environment to you, since, as I mentioned, there is no Global. For example, we can define something like this that 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 the existing use of primitiveBindings with primitiveBindingsWithEval if you want to create a new environment with eval .

+4
source

Your problem (from the error message) readIORef primitiveBindings .

Since readIORef :: IORef a -> IO a and primitiveBindings :: IO (IORef [(String, IORef LispVal)]) types do not line up (the notification primitiveBindings wrapped in IO ).

Try primitiveBindings >>= readIORef .

To the question in your headline: you cannot "get an element from IO", since IO is the world, and haskell does not disclose its implementation, so you are left with the provided interface on Monad (binding and return). Someone may direct you to a better explanation of this point, if necessary.

+3
source

Assumption: the unaryOp type actually includes an IOThrowsError wherever you wrote a ThrowsError . Then, just by going over the types that you specified without knowing the domain, I can assume that you had in mind:

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

Does it look like it's close enough to what you wanted?

+2
source

You mix IO and pure functions in a somewhat random way. Essentially, you have three problems:

  • You want unaryOp (eval (liftIO $ readIORef primitiveBindings)) be of type [LispVal] -> IOThrowsError LispVal , but unaryOp returns the type [LispVal] -> ThrowsError LispVal , and IOThrowsError does not match ThrowsError . There are several ways to fix this, but the simplest is a generalization of the unaryOp type:

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

    (You should probably also get a nicer error message if the second argument is not a single list.)

    Now unaryOp does what it did before, but can also return [LispVal] -> IOThrowsError LispVal if you give it LispVal -> IOThrowsError LispVal , which is exactly what eval gives. So far, so good. But:

  • liftIO (readIORef primitiveBindings) not checked by typecheck because primitiveBindings not an IORef , it is an IO action that gives an IORef when it IORef . That way, you can do an action that reads IORef with =<< , i.e. readIORef =<< primitiveBindings , but you really don't want to do this because you want to pass IORef itself to eval , not its contents . Therefore, you should simply use liftIO primitiveBindings . But:

  • eval takes an argument of type Env , but liftIO primitiveBindings has type IOThrowsError Env (in fact, it is more general than the one, but close enough). Therefore, to retrieve the value, you must first use >>= or do :

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

Aside, to set the tone for the title of the question: in Haskell, you are not getting things from IO . What you do is that you write functions that you then put in the IO to get more IO . >>= (i.e., the notation do ) and return are common methods for "raising" functions to work with IO values.

+2
source

Source: https://habr.com/ru/post/1411654/


All Articles