How can I use the result of an IO action in an non-IO-function? - haskell

This function takes an filepath and returns the content of the file.
this file includes a cuple of lines of the same length.
-- It's used to be an primitive picture.
parsePicture :: FilePath -> IO()
parsePicture fileName = do
content <- lines <$> readFile fileName
print content
Now I've tried to implement a function to flip the "picture" vertically:
type Picture = [[Char]]
flipVertical :: IO() -> Picture
flipVertical xs = map reverse xs
But I only get the following error code:
zuP12.hs:24:31: error:
• Couldn't match expected type ‘[[Char]]’ with actual type ‘IO ()’
• In the second argument of ‘map’, namely ‘xs’
In the expression: map reverse xs
In an equation for ‘flipVertical’: flipVertical xs = map reverse xs
|
24 | flipVertical xs = map reverse xs
| ^^
Failed, no modules loaded.
How can I use my function flipVertical on the result of parsePicture?

This function... returns the content of the file.
No it doesn't. It prints the content of the file, to STDOUT. For most purposes you should consider information you put to STDOUT to be gone – the information has left your program and is now on the terminal screen, or whereever else the user chooses to put it, but not accessible to the program anymore.
(Strictly speaking, it is possible to redirect STDOUT back into your own program, but it's a big hack, don't do this.)
Instead, you should change the function so it actually does return the content:
parsePicture :: FilePath -> IO Picture
parsePicture fileName = do
content <- lines <$> readFile fileName
return content
...or simply
parsePicture fileName = lines <$> readFile fileName
which behaves exactly the same (by the monad laws).
Also, I would rather call this loadPicture: parsing shouldn't involve file reading.
Of course, you can still print the contents later on, with something like
main = do
...
fooPicture <- parsePicture "foofilename"
print fooPicture
...
As for flipVertical, this shouldn't have anything to do with IO at all. It's simply a pure function
flipVertical :: Picture -> Picture
flipVertical xs = map reverse xs
which can be used like, for example
main = do
...
fooPicture <- parsePicture "foofilename"
print $ flipVertical fooPicture
...
or
main = do
...
fooVFPicture <- flipVertical <$> parsePicture "foofilename"
...

Related

How to correctly parse arguments with Haskell?

I'm trying to learn how to work with IO in Haskell by writing a function that, if there is a flag, will take a list of points from a file, and if there is no flag, it asks the user to enter them.
dispatch :: [String] -> IO ()
dispatch argList = do
if "file" `elem` argList
then do
let (path : otherArgs) = argList
points <- getPointsFile path
else
print "Enter a point in the format: x;y"
input <- getLine
if (input == "exit")
then do
print "The user inputted list:"
print $ reverse xs
else (inputStrings (input:xs))
if "help" `elem` argList
then help
else return ()
dispatch [] = return ()
dispatch _ = error "Error: invalid args"
getPointsFile :: String -> IO ([(Double, Double)])
getPointsFile path = do
handle <- openFile path ReadMode
contents <- hGetContents handle
let points_str = lines contents
let points = foldl (\l d -> l ++ [tuplify2 $ splitOn ";" d]) [] points_str
hClose handle
return points
I get this: do-notation in pattern Possibly caused by a missing 'do'?` after `if "file" `elem` argList.
I'm also worried about the binding issue, assuming that I have another flag that says which method will be used to process the points. Obviously it waits for points, but I don't know how to make points visible not only in if then else, constructs. In imperative languages I would write something like:
init points
if ... { points = a}
else points = b
some actions with points
How I can do something similar in Haskell?
Here's a fairly minimal example that I've done half a dozen times when I'm writing something quick and dirty, don't have a complicated argument structure, and so can't be bothered to do a proper job of setting up one of the usual command-line parsing libraries. It doesn't explain what went wrong with your approach -- there's an existing good answer there -- it's just an attempt to show what this kind of thing looks like when done idiomatically.
import System.Environment
import System.Exit
import System.IO
main :: IO ()
main = do
args <- getArgs
pts <- case args of
["--help"] -> usage stdout ExitSuccess
["--file", f] -> getPointsFile f
[] -> getPointsNoFile
_ -> usage stderr (ExitFailure 1)
print (frobnicate pts)
usage :: Handle -> ExitCode -> IO a
usage h c = do
nm <- getProgName
hPutStrLn h $ "Usage: " ++ nm ++ " [--file FILE]"
hPutStrLn h $ "Frobnicate the points in FILE, or from stdin if no file is supplied."
exitWith c
getPointsFile :: FilePath -> IO [(Double, Double)]
getPointsFile = {- ... -}
getPointsNoFile :: IO [(Double, Double)]
getPointsNoFile = {- ... -}
frobnicate :: [(Double, Double)] -> Double
frobnicate = {- ... -}
if in Haskell doesn't inherently have anything to do with control flow, it just switches between expressions. Which, in Haskell, happen to include do blocks of statements (if we want to call them that), but you still always need to make that explicit, i.e. you need to say both then do and else do if there are multiple statements in each branch.
Also, all the statements in a do block need to be indented to the same level. So in your case
if "file" `elem` argList
...
if "help" `elem` argList
Or alternatively, if the help check should only happen in the else branch, it needs to be indented to the statements in that do block.
Independent of all that, I would recommend to avoid parsing anything in an IO context. It is usually much less hassle and easier testable to first parse the strings into a pure data structure, which can then easily be processed by the part of the code that does IO. There are libraries like cmdargs and optparse-applicative that help with the parsing part.

Good practice to write to a file

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.

How to use readFile

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]

Haskell Read Integers from a file to a list

I have a simple text file with one line:
6 195 265 750 265 750 196
I have a function:
executeList :: Integer -> [Integer] -> [String]
executeList n x = [reverseAndAdd n i | i <- x]
That takes an integer, list of integer and returns an array of Strings.
What I want to do, is to read that text file to and [Integer] list and pass that to executeList function.
Here is my code:
main = do
let list = []
handle <- openFile "data.txt" ReadMode
contents <- hGetContents handle
let singlewords = words contents
list = f singlewords
print list
hClose handle
f :: [String] -> [Integer]
f = map read
I found it here:
Haskell file reading
When I run 'main' I get this output:
[6,195,265,750,265,750,196]
but when I try to pass it like this to executeList:
let list = main
executeList 0 list
I get this error:
<interactive>:103:15: error:
* Couldn't match expected type `[Integer]' with actual type `IO ()'
* In the second argument of `executeList', namely `list'
In the expression: executeList 0 list
In an equation for `it': it = executeList 0 list
If I check the type of that list, i get this:
list :: IO()
I looked up on the internet for how to transform IO() to [Integer] but found nothing useful. Maybe someone can show me the way to do that conversion?
The short answer is that you can't transform IO() into [Integer].
It seems as though you are misunderstanding the IO monad. Most functions return a value. Functions with a return type of IO a instead return an I/O action that performs some sort I/O before returning a value of type a. In your case IO () is an I/O action that will return () which is just an empty tuple. When you are writing console programs like this that read in data and then print out some results you'll typically follow this pattern:
Read input from file or command line
Pass data to function for computation
Print results
Your whole program will end up living inside of the IO monad. do is a notation that is a syntactic sugar for the bind operator >>=. This operator allows us to chain monadic computations together. The <- in your code extracts a value from a monad (in your case an IO action) and stores it in a variable. Lets take a look at the type signature of hGetContents. From GHCI we can learn that this function has a type of hGetContents :: Handle -> IO String It takes a Handle and returns an I/O action that returns a string. When you call contents <- hGetContents handle the program calls hGetContents with the file handle you specified and then extracts a string from the IO action that is returned and stores that string in the variable contents. So now you've read the input. After you've converted the numbers to actual integer types the next step is to call your function which is the simple call let data = executeList 0 list From there you can output you data with print data. It's important to keep in mind that the whole time you are in the IO monad. In the end your entire main function should look something like this:
main = do
handle <- openFile "data.txt" ReadMode
contents <- hGetContents handle
let singlewords = words contents
list = f singlewords
data = executeList 0 list
print data
hClose handle
f :: [String] -> [Integer]
f = map read

Read array-string into variable

I have text file containing data like that:
13.
13.
[(1,2),(2,3),(4,5)].
And I want to read this into 3 variables in Haskell. But standard functions read this as strings, but considering I get rid of dot at the end myself is there any built-in parser function that will make Integer of "13" and [(Integer,Integer)] list out of [(1,2),(2,3),(4,5)] ?
Yes, it's called read:
let i = read "13" :: Integer
let ts = read "[(1,2),(2,3),(4,5)]" :: [(Integer, Integer)]
The example text file you gave has trailing spaces as well as the full stop, so merely cutting the last character doesn't work. Let's take just the digits, using:
import Data.Char (isDigit)
Why not have a data type to store the stuff from the file:
data MyStuff = MyStuff {firstNum :: Int,
secondNum:: Int,
intPairList :: [(Integer, Integer)]}
deriving (Show,Read)
Now we need to read the file, and then turn it into individual lines:
getMyStuff :: FilePath -> IO MyStuff
getMyStuff filename = do
rawdata <- readFile filename
let [i1,i2,list] = lines rawdata
return $ MyStuff (read $ takeWhile isDigit i1) (read $ takeWhile isDigit i2) (read $ init list)
The read function works with any data type that has a Read instance, and automatically produces data of the right type.
> getMyStuff "data.txt" >>= print
MyStuff {firstNum = 13, secondNum = 13, intPairList = [(1,2),(2,3),(4,5)]}
A better way
I'd be inclined to save myself a fair bit of work, and just write that data directly, so
writeMyStuff :: FilePath -> MyStuff -> IO ()
writeMyStuff filename somedata = writeFile filename (show somedata)
readMyStuff :: FilePath -> IO MyStuff
readMyStuff filename = fmap read (readFile filename)
(The fmap just applies the pure function read to the output of the readFile.)
> writeMyStuff "test.txt" MyStuff {firstNum=12,secondNum=42, intPairList=[(1,2),(3,4)]}
> readMyStuff "test.txt" >>= print
MyStuff {firstNum = 12, secondNum = 42, intPairList = [(1,2),(3,4)]}
You're far less likely to make little parsing or printing errors if you let the compiler sort it all out for you, it's less code, and simpler.
Haskell's strong types require you to know what you're getting. So let's forgo all error checking and optimization and assume that the file is always in the right format, you can do something like this:
data Entry = Number Integer
| List [(Integer, Integer)]
parseYourFile :: FilePath -> IO [Entry]
parseYourFile p = do
content <- readFile p
return $ parseYourFormat content
parseYourFormat :: String -> [Entry]
parseYourFormat data = map parseEntry $ lines data
parseEntry :: String -> Entry
parseEntry line = if head line == '['
then List $ read core
else Number $ read core
where core = init line
Or you could write a proper parser for it using one of the many combinator frameworks.

Resources