Store Handle in custom data field? - haskell

One more stupid question =) I have a custom data type with Handle field:
import System.IO
data CustomType = CustomType {
file::Handle
}
How can I set a file field? I'm trying to use this obvious code:
let someFile = openFile fileName AppendMode
let object = CustomType {
file=someFile
}
but openFile has a type openFile :: FilePath -> IOMode -> IO Handle, so I've got an error
Couldn't match expected type `Handle' with actual type `IO Handle'
So how can I store Handle object in this field?
UPD
I'm trying also this
data CustomType = CustomType {
file::IO Handle
}
but this results to an error, when I'm using the hPutStrLn function
let object = CustomType {
file=someFile
}
hPutStrLn (file object)
Error message is:
Couldn't match expected type `Handle' with actual type `IO Handle'
In the return type of a call of `file'
In the first argument of `TO.hPutStrLn', namely `(file object)'
In a stmt of a 'do' block:
TO.hPutStrLn (file object) text

I'm not completely sure what you want. If you don't understand type errors involving IO type mismatches, you should probably read an introduction to IO in Haskell first. Anyway, this is code that works:
import System.IO
data CustomType = CustomType {
file :: Handle
}
fileName :: FilePath
fileName = "foo"
process :: IO ()
process = do
someFile <- openFile fileName AppendMode
let object = CustomType { file = someFile }
hPutStrLn (file object) "abc"
hClose (file object)
If you want to type the commands in GHCi instead, you can enter every line of the do-block in the process action individually, like this:
GHCi> someFile <- openFile fileName AppendMode
GHCi> let object = CustomType { file = someFile }
GHCi> hPutStrLn (file object) "abc"
GHCi> hClose (file object)

So you have created your type like this:
data CustomType = CustomType {
file::Handle
}
Now try this in ghci:
ghci> let a = openFile "someFile.txt" AppendMode
ghci> :t a
a :: IO Handle
So Handle is wrapped with IO type. You can use the Monad bind operator
to extract the Handle out of it.
ghci> let b = a >>= \handle -> return $ CustomType handle
The return function will wrap the CustomType again in the IO monad. You can verify this again in the ghci:
ghci> :t b
b :: IO CustomType
The type of bind or >>= is like this:
ghic> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Try to replace m with IO and you get:
(>>=) :: IO a -> (a -> IO b) -> IO b
And that's why you have to use return function with the bind operator, so that it will typecheck.

So your confusion seems to be with how to use values inside a IO monad. The thing about the IO monad is that its infectious, so whenever you want to do something with a IO value you will get a IO result. This might sound like a pain in the ass, but its actually pretty nice in reality as it keeps the parts of your program pure and gives you complete control over when actions are performed. Instead of trying to get out of the IO monad you have to learn to embrace the haskell way of applying functions to monadic values. Every monad is a functor so you can apply a pure function to a monadic value with fmap. The extra power a monad gives is that it allows you to join contexts together. So if you have a IO a and a IO b you can join the IO's together to get IO (a, b). We can use this knowledge to solve your problem:
openFile has the signature:
openFile :: FilePath -> IOMode -> IO Handle
As mentioned above there is no way* to remove the IO from Handle, so the only thing you can do is to put your type inside the IO monad too. You can for example make this function that uses fmap to apply your object to the IO Handle:
createMyObject :: FilePath -> IO CustomType
createMyObject fp = CustomType `fmap` openFile fp AppendMode
Now you have your object but its in a IO monad so how do you use it? The outermost layer of your application is always in the IO monad. So your main function should have a signature like IO (). Inside the main function you can use other IO values like they are pure by using do notation. The (<-) keyword is kind of like join we talked about above. It draws the value from another IO into the current IO:
main :: IO ()
main = do
myObjectPure <- createMyObject "someFilePath.txt"
let myHandle :: Handle -- No IO!
myHandle = file myObjectPure
-- Now you can use the functions that takes a pure handle:
hPutStrLn myHandler "Yay"
By the way you probably shouldn't use a Handle directly this way because it will be really easy for you to forget to close it. Its better to use something like withFile which will close the handle for you when its done.
*Actually there is a way, but you don't need to know about it yet because its very unlikely that you are solving a problem that actually needs it, and its too easy to abuse for someone new.

Related

Is it possible to use writeFile inside out of the main function (in haskell)?

I am still a beginner in Haskell, so after reading some writefile tutorials online, I see most of the writefile examples online are used inside the main function (main = IO ())
I am wondering whether it's possible to write a function that writes the results into a file using writefile when it is computed? In some programs (especially games), users might want to stop at a particular points of the game by saving the contents into a .txt file.
For example something like this: (this function does not work, just wondering how to make it work)
concat :: FilePath -> [[a]] -> [a]
concat txt [] = []`
concat txt (xs : xss) = do
y <- xs ++ concat xss
writeFile txt (unlines y)
Thanks:)
The writeFile function has the type FilePath -> String -> IO (), which means that it must run in the IO context.
It doesn't have to run in the main function, but any function that involves IO, including writeFile, will have a return type that involves IO. So you could definitely do something like this:
myFunc :: String -> IO ()
myFunc contents = do
-- do something else
writeFile "foo.txt" contents
-- do more stuff here
You can't, however, call functions that return IO a from pure functions (or, rather, you can't extract the value from the IO container). That's by design; it's how Haskell works, and it's a good thing. If you want to enable users to perform impure actions at arbitrary times, you must design for such a feature. Here's my introduction to pure interactions - that's one place to start.
Yes, you can use writeFile in other places than main, but for a place to qualify, the type IO has to be a part of that place's type signature. (The reason I'm saying place is because main isn't a function, but your concat is a function. And the place you want to look at putting your writeFile call has to be an IO action, which can be the result of a function or not.)
You mentioned saving something related to a game into a .txt file. An example of that could be:
saveGame :: FilePath -> GameState -> IO ()
saveGame gameFile gameState =
writeFile gameFile (serializeGame gameState)
serializeGame :: GameState -> String
serializeGame (GameState ...) = ...
runGame :: GameState -> IO ()
runGame gameState = do
...
if wantsToSaveGame
then saveGame gameFile gameState
else ...
...
runGame updatedGameState
main :: IO ()
main = do
...
runGame initialGameState
In this contrived example, serializeGame would not be a suitable place to call saveGame because it's a pure function, whereas runGame is a self-recursive IO () action capable of affecting files on your file system.
An example of a related IO action that isn't a function could be this one:
resetSaveGame :: IO ()
resetSaveGame =
saveGame defaultGameFile initialGameState

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]

Writing an IO String to stdout in Haskell

How do we print the output of a function that returns an IO String to the stdout? I am not able to use show or print.
If you want to print the result of the function foo :: Int -> IO String (for example), you can do
main = do
str <- foo 12
putStrLn str
or, without the do-notation,
main = foo 12 >>= putStrLn.
The do-notation is syntactic sugar for the second, which uses the fundamental (>>=) combinator, which has the type
(>>=) :: Monad m => m a -> (a -> m b) -> m b
IO is an instance of the Monad class, so you can use it here.
foo :: Int -> IO String
foo 12 :: IO String
putStrLn :: String -> IO ()
(foo 12) >>= putStrLn :: IO ()
How do we print the output of a function that returns an IO String to the stdout?
Well let's see. Here's a function that returns an IO String:
dumbFunction :: a -> IO String
dumbFunction x = getLine
dumbFunction is a dumb function (but a function nonetheless!). It ignores its input, and then returns getLine, which has the type IO String.
So you tell me, how do you print getLine :: IO String? The answer is, you don't! This is what we call an "IO action". Note that an IO action is not a function, because it does not take input. (However, IO actions might acquire input from, well, IO operations such as reading stdin, as getLine does. But it is not considered a "function" because it does not accept any traditional input)
So instead of printing out the action itself, you probably want to run the action, and then print the result. This can be done as Daniel Fischer described (with <-, which can be thought of as the "run" operator).

How withFile is implemented in haskell

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.

Haskell IO (String) and String

I want to write functions and put result to string.
I want function:
read' :: FilePath -> String
I use:
:t readFile
readFile :: FilePath -> IO String
I make:
read' :: IO ()
read' = do
str <- readFile "/home/shk/workspace/src/test.txt"
putStrLn str
I want to ask str is string or not?
We know that:
:t putStrLn
putStrLn :: String -> IO ()
Then why i can't:
read' :: String
read' = do
str <- readFile "/home/shk/workspace/lxmpp/src/test.txt"
str
I get error that:
Couldn't match expected type `[t0]' with actual type `IO String'
In the return type of a call of `readFile'
In a stmt of a 'do' expression:
str <- readFile "/home/shk/workspace/lxmpp/src/test.txt"
In the expression:
do { str <- readFile "/home/shk/workspace/src/test.txt";
str }
Thank you.
Just to quibble a bit more, while the other answers are perfectly correct, I want to emphasize something: Something with the type IO String isn't just a string that the type system won't let you get at directly. It's a computation that performs I/O to get a string for you. Applying readFile to a file path doesn't return a String value any more than putting a steak next to a meat grinder magically turns them into a hamburger.
When you have some code like this:
foo = do let getStr = readFile "input.txt"
s1 <- getStr
s2 <- getStr
-- etc.
That doesn't mean you're "taking the string out of getStr twice". It means you're performing the computation twice and can easily get different results between the two.
I think no one has answered this, very important question, yet:
I want to ask str is string or not?
I will try to.
The type of the variable str is String, yes.
However, the scope of this variable is very limited. I think desugaring the do-notation is necessary for understanding:
read' = readFile "/home/shk/workspace/src/test.txt" >>= (\str -> putStrLn str)
I think here it becomes more clear why str is not good enough. It is an argument of the function you pass to >>=. Its value only becomes available when someone calls your function, which happens only when the IO action containing it is being executed.
Also, the type of read' :: IO () is determined not so much by the putStrLn str, but rather by the return type of the operator >>=. Have a look at it (specialized to the IO monad):
(>>=) :: IO a -> (a -> IO b) -> IO b
You can see that the result is always an IO b action, so trying to change any of arguments won't help.
You can read some monad tutorial if you want to understand why the type is the way it is. The intuition behind it is: you can't perform an action without performing an action.
And on the practical side of the question, to use the value returned by some action, instead of trying to do use (extractValue inputAction), which does not make sense because extractValue is not possible, try inputAction >>= use if your use does involve I/O, or fmap use inputAction if it does not.
You should use return str in read' if you want it to return str instead of (). You can't strip IO from the type of read', since it's not a pure function. To get a better grip on how input/output in Haskell works I recommend you to read a tutorial.
As a more detailed reason why: It allows impurity.
You absolutely can not perform IO during a pure operation, or it would completely break referential transparency. Technically you can use unsafePerformIO but it would break referential transparency in this case - you should only use that if you can guarantee that the result is always the same.

Resources