Интуиция в разборе формата 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 Вы также можете проверить этот ответ для более приземленной точки зрения.