In the main function of my program, I call timesRule which is returning a Boolean value. In this function I want to write to a file. However, if I understood correctly the function times rule needs to return IO() if it writes to file.
How should I structure my code to write to a file in a function returning a Boolean value ?
timesRule :: (MultiSet LocalType) -> Bool
timesRule sequent = do
let result = MultiSet.concatMap (\x -> if isPrl x then [checkTimes x, checkTimes2 x] else [x] ) sequent
let file = "tmp/log.txt"
let content = "TIMES rule: " ++ (show(MultiSet.toList result))
let log = writeToFile file content
prefixRule result
Used function:
import qualified System.IO.Strict as SIO
writeToFile :: FilePath -> String -> IO()
writeToFile file content = do
x <- SIO.readFile file
writeFile file ("\n"++content)
appendFile file x
The somewhat obvious solution would be changing the type your function to IO Bool as #Robin Zigmond pointed out.
There is some problem with your syntax apart from calling writeToFile, though. For your function timesRule to have the given type it would need to look like this:
timesRule :: (MultiSet LocalType) -> Bool
timesRule sequent = -- there is no do here
let
result = MultiSet.concatMap (\x -> if isPrl x then [checkTimes x, checkTimes2 x] else [x] ) sequent
-- the following two lines are superfluous ...
file = "tmp/log.txt"
content = "TIMES rule: " ++ (show(MultiSet.toList result))
-- ... because this still doesn't work
log = writeToFile file content
-- ... and what were you going to use `log` for, anyway?
in
prefixRule result
Changing your type to IO Bool allows you to use monadic do-syntax. Bool by itself neither has an applicative nor a monad instance and thus there is no meaningful do-syntax. (In order to have either an applicative or a monad instance, you need a type function like Maybe or IO, fyi):
timesRule :: (MultiSet LocalType) -> IO Bool
timesRule sequent = do
let
result = MultiSet.concatMap (\x -> if isPrl x then [checkTimes x, checkTimes2 x] else [x] ) sequent
file = "tmp/log.txt"
content = "TIMES rule: " ++ (show(MultiSet.toList result))
-- the syntax to get the value of type `a` out of `IO a` is this:
log <- writeToFile file content
-- the function to turn a value of type `a` into `IO a` is `pure`:
pure (prefixRule result)
You stil don't use log and might as well replace
log <- writeToFile file content
with
writeToFile file content
Given that writeToFile has type ... -> IO (), the () is pronounced "unit", the value of log is () and thus log does not contain any useful information (probably).
The less obvious solution is to refactor your code a bit and seperate the concerns. Sometimes it does make sense to have a function write to a file and return some boolean value. In your case, you probably want a funcion that returns result, i.e. turn this line into a function:
MultiSet.concatMap (\x -> if isPrl x then [checkTimes x, checkTimes2 x] else [x] ) sequent
Then you already have prefixRule that gives you the Bool and you have writeFile. This way you separate pure code (anything that does not have the type IO something) form code with IO-side effects.
Related
I am having trouble reading in a level file in Haskell. The goal is to read in a simple txt file with two numbers seperated by a space and then commas. The problem I keep getting is this: Couldn't match type `IO' with `[]'
If I understand correctly the do statement is supposed to pull the String out of the Monad.
readLevelFile :: FilePath -> [FallingRegion]
readLevelFile f = do
fileContent <- readFile f
(map lineToFallingRegion (lines fileContent))
lineToFallingRegion :: String -> FallingRegion
lineToFallingRegion s = map textShapeToFallingShape (splitOn' (==',') s)
textShapeToFallingShape :: String -> FallingShape
textShapeToFallingShape s = FallingShape (read $ head numbers) (read $ head
$ tail numbers)
where numbers = splitOn' (==' ') s
You can't pull things out of IO. You can think of IO as a container (in fact, some interpretations of IO liken it to the box containing Schrödinger's cat). You can't see what's in the container, but if you step into the container, values become visible.
So this should work:
readLevelFile f = do
fileContent <- readFile f
return (map lineToFallingRegion (lines fileContent))
It does not, however, have the type given in the OP. Inside the do block, fileContent is a String value, but the entire block is still inside the IO container.
This means that the return type of the function isn't [FallingRegion], but IO [FallingRegion]. So if you change the type annotation for readLevelFile to
readLevelFile :: FilePath -> IO [FallingRegion]
you should be able to get past the first hurdle.
Let's look at your first function with explicit types:
readLevelFile f = do
(fileContent :: String) <-
(readFile :: String -> IO String) (f :: String) :: IO String
fileContent is indeed of type String but is only available within the execution of the IO Monad under which we are evaluating. Now what?
(map lineToFallingRegion (lines fileContent)) :: [String]
Now you are suddenly using an expression that is not an IO monad but instead is a list value - since lists are also a type of monad the type check tries to unify IO with []. What you actually wanted is to return this value:
return (map lineToFallingRegion (lines fileContent)) :: IO [String]
Now recalling that we can't ever "exit" the IO monad your readLevelFile type must be IO - an honest admission that it interacts with the outside world:
readLevelFile :: FilePath -> IO [FallingRegion]
I have a simple function like:
nth :: Integer -> Integer
And I try to print it's result as follows:
main = do
n <- getLine
result <- nth (read n :: Integer)
print result
The following error is generated:
Couldn't match expected type `IO t0' with actual type `Integer'
In the return type of a call of `nth'
In a stmt of a 'do' expression:
result <- nth (read n :: Integer)
Also tried with putStrLn and a lot of other combinations with no luck.
I can't figure it out and I would need some help, being that I don't fully understand how stuff works around these IOs.
nth is a function, not an IO action:
main = do
n <- getLine
let result = nth (read n :: Integer)
print result
The do syntax unwraps something within a monad. Everything on the right hand side of the arrow must live within the IO monad, otherwise the types don't check. An IO Integer would be fine in your program. do is syntactic sugar for the more explicit function which would be written as follows:
Recall that (>>=) :: m a -> (a -> m b) -> m b
main = getLine >>= (\x ->
nth >>= (\y ->
print y))
But nth is not a monadic value, so it doesn't make sense to apply the function (>>=), which requires something with the type IO a.
Following a haskell tutorial, the author provides the following implementation of the withFile method:
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
handle <- openFile path mode
result <- f handle
hClose handle
return result
But why do we need to wrap the result in a return? Doesn't the supplied function f already return an IO as can be seen by it's type Handle -> IO a?
You're right: f already returns an IO, so if the function were written like this:
withFile' path mode f = do
handle <- openFile path mode
f handle
there would be no need for a return. The problem is hClose handle comes in between, so we have to store the result first:
result <- f handle
and doing <- gets rid of the IO. So return puts it back.
This is one of the tricky little things that confused me when I first tried Haskell. You're misunderstanding the meaning of the <- construct in do-notation. result <- f handle doesn't mean "assign the value of f handle to result"; it means "bind result to a value 'extracted' from the monadic value of f handle" (where the 'extraction' happens in some way that's defined by the particular Monad instance that you're using, in this case the IO monad).
I.e., for some Monad typeclass m, the <- statement takes an expression of type m a in the right hand side and a variable of type a on the left hand side, and binds the variable to a value. Thus in your particular example, with result <- f handle, we have the types f result :: IO a, result :: a and return result :: IO a.
PS do-notation has also a special form of let (without the in keyword in this case!) that works as a pure counterpart to <-. So you could rewrite your example as:
withFile' :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withFile' path mode f = do
handle <- openFile path mode
let result = f handle
hClose handle
result
In this case, because the let is a straightforward assignment, the type of result is IO a.
checkstring :: [String] -> Int -> [String]
checkstring p n = do z <- doesFileExist (p !! n)
if z
then p
else error $ "'" ++ (p !! n) ++ "' file path does not exist"
It checks for a element in the string by looking at "n"(so if n = 2 it will check if the second string in the list) then see if it exists. If it does exist it will return the original string list, if not it will error.Why does it do this? :
Couldn't match expected type `[t0]' with actual type `IO Bool'
In the return type of a call of `doesFileExist'
In a stmt of a 'do' expression: z <- doesFileExist (p !! n)
The type of doesFileExist is String -> IO Bool. If your program wants to know whether a file exists, it has to interact with the file system, which is an IO action. If you want your checkString function to do that, it will also have to have some kind of IO-based type. For example, I think something like this would work, though I haven't tried it:
checkstring :: [String] -> Int -> IO [String]
checkstring p n = do z <- doesFileExist (p !! n)
if z
then return p
else error $ "'" ++ (p !! n) ++ "' file path does not exist"
To add to what MatrixFrog has mentioned in his answer. If you look at your function signature i.e [String] -> Int -> [String] it indicates that this function is a pure function and doesn't involved any side effects, where as in your function body you are using doesFileExist which has a signature of String -> IO Bool where the presence of IO indicates it is a impure function i.e it involves some IO. In haskell there is a strict separation between impure and pure functions and as a matter of fact if your function calls some other function which is impure than your function is also impure. So in your case your function checkString needs to be impure and that can be done by making it return IO [String], which is what MatrixFrog has mentioned in his answer.
On another note, I would suggest that you can make the function to be something like:
checkString :: String -> IO (Maybe String) ,as your function doesn't need the whole list of string as it just need a specific string from the list to do its work and rather than throwing an error you can use Maybe to detect the error.
This is just a suggestion but it also depends on how your function is being used.
I think the problem is that your type signature forces the do block to assume that it is some other monad. For example, suppose you're working in the list monad. Then, you could write
myFcn :: [String] -> Int -> [String]
myFcn p n = do
return (p !! n)
In the case of the list monad, the return simply returns the singleton list, so you get behavior like,
> myFcn ["a", "bc", "d"] 1
["bc"]
(My personal opinion is that it would be very helpful if the GHC had an option to print out common mistakes that could cause a type error; I sympathize with the asker in that I've gotten a lot of type error messages that take time to figure out).
Alright so here's my current code:
import System.IO
import System.Environment
import System.Directory
main = do
unfiltered <- getArgs ; home <- getHomeDirectory ; let db = home ++ "/.grindstone"
case unfiltered of
(x:xs) -> return ()
_ -> error "No command given. See --help for more info."
command:args <- getArgs
createDirectoryIfMissing True db
let check = case args of
[] -> error "No arguments given. See --help for more info."
_ -> do let (params#(param:_),rest) = span (\(c:_) -> c=='-') args
if length params > 1 then error ("No arguments given for " ++ param)
else do
let (pArgs,_) = span (\(c:_) -> c/='-') rest
return (param, pArgs) :: Either (IO ()) (String, [String])
let add = print "sup"
let cmds = [("add", add)]
let action = lookup command cmds
case action of
Nothing -> error "Unknown command."
(Just action) -> action
The main problem is with check. I tried implementing the Either type since I want it to either error out, or return something for another function to use, but, it's currently erroring out with:
grindstone.hs:21:23:
No instance for (Monad (Either (IO ())))
arising from a use of `return' at grindstone.hs:21:23-43
Possible fix:
add an instance declaration for (Monad (Either (IO ())))
In the expression:
return (param, pArgs) :: Either (IO ()) (String, [String])
In the expression:
do { let (pArgs, _) = span (\ (c : _) -> ...) rest;
return (param, pArgs) :: Either (IO ()) (String, [String]) }
In the expression:
if length params > 1 then
error ("No arguments given for " ++ param)
else
do { let (pArgs, _) = ...;
return (param, pArgs) :: Either (IO ()) (String, [String]) }
I'm only starting out in haskell and haven't dealt too much with monads yet so just thought I'd ask on here. anyone have any ideas?
The error that is causing your compile problems is that you are directly casting an expression to the type Either (IO ()) (String, [String]) when it is not an Either value. (The compiler is not outputting a very helpful error message.)
To create an Either value [1], we use the data constructors Left and Right. Convention (from the library page) is that errors are a Left value, while correct values are a Right value.
I did a quick rewrite of your arg checking function as
checkArgs :: [String] -> Either String (String, [String])
checkArgs args =
case args of
[] -> Left "No arguments given. See --help for more info."
_ -> let (params#(param:_),rest) = span (\(c:_) -> c=='-') args in
if length params > 1 then
Left ("No arguments given for " ++ param)
else
let (pArgs,_) = span (\(c:_) -> c/='-') rest in
Right (param, pArgs)
Note that the arg checking function does not interact with any external IO () library functions and so has a purely functional type. In general if your code does not have monadic elements (IO ()), it can be clearer to write it in purely functional style. (When starting out in Haskell this is definitely something I would recommend rather than trying to get your head around monads/monad transformers/etc immediately.)
When you are a little more comfortable with monads, you may want to check out Control.Monad.Error [2], which can wraps similar functionality as Either as a monad and would encapsulate some details like Left always being computation errors.
[1] http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/Data-Either.html
[2] http://hackage.haskell.org/packages/archive/mtl/1.1.0.2/doc/html/Control-Monad-Error.html
Either (IO ()) (String, [String]) is a type that contains an IO action or a
(String, [String]), so values of this type could be Left IO () or
Right (String, [String]). Left values usually represents an error
occurrence in Haskell. This error can be represented with any type you want,
for example, an error code (Int) or a String that says what happened.
If you use IO () as the type which represents an error, you won't be able
to extract any information about the error. You just will able to perform an IO action later on.
The type that you are looking for isn't Either (IO ()) (String, [String]),
is Either String (String, [String]). With this type can get information about the
error (String). Now, you dont need any IO action into Either type, so you
can remove all do expressions:
let check = case args of
[] -> Left "No arguments given. See --help for more info."
_ -> let (params#(param:_),rest) = span (\(c:_) -> c=='-') args
in if length params > 1
then Left ("No arguments given for " ++ param)
else let (pArgs,_) = span (\(c:_) -> c/='-') rest
in Right (param, pArgs)