Интуиция в разборе формата Graph Dimacs в Haskell

Я новичок в Хаскеле, и я пытаюсь выучить несколько концепций, чтобы оценить вашу помощь. Я прочитал несколько книг и руководств, но я не могу понять, как я могу использовать операции ввода-вывода. Я пытаюсь разобрать файл в этом формате и создать объект графа. Пока у меня есть этот код: (узлы идентифицируются положительным целым числом)

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

Я понимаю, что haskell ленив. Я хочу создать функцию с подписью

parseInput :: String -> Maybe Graph

так что я либо получаю Nothing на ошибку или график. Моя проблема в том, что я не могу просто прочитать файл и создать график в одной функции. Насколько я понимаю, я должен получить IO String, создать объект, а затем закрыть файл с помощью дескриптора? Кто-нибудь может указать мне правильное направление? Мне трудно с этим, потому что это сильно отличается от императивных языков.

Спасибо за любые предложения.

1 ответ

Решение

Вы можете думать об этом как о двух разных мирах:

  • детерминированный, обычный мир, где живут все чистые функции, и
  • волшебный мир ввода-вывода, где живут файлы, и где вы действительно не знаете, что может произойти.

Когда кто-то говорит IO это монада, вы можете понять это так: вы можете сделать что-нибудь магическое IO, но как только заклинание разыграно, пути к обычному нет.

Итак, функции типа IO ваши заклинания, а parseInput это обычное, полностью предсказуемое механическое устройство: для любого заданного входа вы всегда можете знать, каким будет выход. После того, как вы установили свою механику, вы должны наколдовать contents с readFileи затем перенесите свою обычную функцию в волшебный мир ввода-вывода, а также fmap:

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

magicallyGetContents :: IO String
magicallyGetContents = readFile ...

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

К этому моменту вы можете применить свой парсер:

λ :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)

В вашем распоряжении есть несколько заклинаний, которые могут помочь вам перейти на магическую сторону. Самый простой будет:

return :: a -> IO a

(Название предполагает, что волшебный мир - это дом всех вещей в Хаскеле.)

Обратите внимание, что как только вы примените это довольно простое заклинание, пути назад уже не будет. Хотя теоретически вы можете взломать свой путь через ОЗУ и получить байты, это не будет простым делом. Например, это не будет делать:

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

Итак, вы можете предположить, что у вас есть содержимое в чистом мире, и построить свою машину, которая затем будет работать, затем пересечь межмирочный барьер с ней и применить ее к любому количеству графических файлов DIMACS, находящихся на другой стороне.

Счастливые приключения!

ps Вы также можете проверить этот ответ для более приземленной точки зрения.

Другие вопросы по тегам