1

I'm a beginner in Haskell, and I'm trying to learn few concenpts so I would appretiate your help. I read few books and guides but I can't understand how can I use IO operations. I'm trying to parse file in this format and create graph object. So far I have this code: (nodes are identified by positive integer)

data Edge = Edge Int Int deriving(Show)

data Graph = Graph {
                nodeCount :: Int,
                edges :: [Edge]
            }   deriving (Show)     

parseInput :: String -> IO ()
parseInput filePath = do
    handle <- openFile filePath ReadMode
    contents <- hGetContents handle
    putStrLn contents
    hClose handle

I understand that haskell is lazy. I want to create function with signature

parseInput :: String -> Maybe Graph

so I either get Nothing on error or Graph. My problem is that I can't simply read the file and create the graph in one function. As far as I understand, I'm supposed to get IO String, create the object and then close the file using handle? Could anyone point me in the right direction? I'm having a hard time with this because it's very different from imperative languages.

Thanks for any suggestions.

Matúš Bako
  • 388
  • 2
  • 15
  • What do you want to do with the `Maybe Graph` that you get? Why not just put the line `print (parseInput contents)` in your `do`-block? – AJF Feb 13 '18 at 10:57
  • 1
    You can just pass the `contents` in your example code to your parseInput function `let maybeGraph = parseInput contents`. Are you sure your problem is the IO? Because the IO in your example is fine. If you're asking how to write a parser in Haskell, that's an entirely different question. As an aside, Haskell is lazy but IO isn't necessarily lazy, `bytestring` especially can be used for strict IO. – Cubic Feb 13 '18 at 10:58

1 Answers1

1

You can think of it as two different worlds:

  • the deterministic, ordinary world where all pure functions live, and
  • the magical IO world where files live, and where you don't really know what could happen.

When someone says IO is a monad, you can understand it thus: you can make anything a magical IO thing, but once the spell is cast, there's no way back to the ordinary.

So, functions of type IO are your spells, and the parseInput is an ordinary, totally predictable mechanical device: for any given input, you may always know what the output will be. Once you have your mechanics in place, you have to conjure some contents with readFile, and then move your ordinary function to the magical IO world as well with fmap:

parseInput :: String -> Maybe Graph
parseInput = ...

magicallyGetContents :: IO String
magicallyGetContents = readFile ...

parseMagicalInput :: IO String -> IO (Maybe Graph)
parseMagicalInput = fmap parseInput

By this point, you can apply your parser:

λ :t parseInput magicallyGetContents
    -- This won't work because of the interworld barrier.

<interactive>... error:
    • Couldn't match type ...
      Expected type: String
        Actual type: IO String
...

λ :t parseMagicalInput magicallyGetContents
    -- This is fine, because both things are on the magical side.
parseMagicalInput  magicallyGetContents :: IO (Maybe Graph)

There are several spells at your disposal that can help you cross to the magical side. The simplest would be:

return :: a -> IO a

(The name suggests the magical world is actually the home of all things in Haskell.)

Notice that once you apply this fairly simple spell, there is no way back. Though you theoretically could hack your way through the RAM and retrieve the bytes, it will not be straightforward. For example, this won't do:

λ print $ (unsafeCoerce (return 2 :: IO Int) :: Int)
1099511628032

So, your way is to assume you have the contents in the pure world, and build your machine that would then work, then cross the interworld barrier with it and apply it to any number of DIMACS graph files there on the other side.

Happy adventures!

p.s. You may also check this answer for a more down-to-earth point of view.

duplode
  • 33,731
  • 7
  • 79
  • 150
Ignat Insarov
  • 4,660
  • 18
  • 37
  • 1
    So, tldr, because I performed IO operation (and result is unknown before performing it), my Graph is stuck with IO prefix (also called monad?) and I can't go back. Thanks for the intuition. I'm trying to code it right now so I'll probably post my code soon. – Matúš Bako Feb 14 '18 at 11:57
  • @MatúšBako Not only is it unknown, you may not even get the file at all. For example, if the previously valid file gets removed by someone else just before your program tries to access it. Pure things always do their job, yet IO things you should expect to fail you any moment. – Ignat Insarov Feb 14 '18 at 13:41