在Haskell中处理嵌套IO的惯用方法

Nat*_*way 10 haskell

我正在学习Haskell,并编写一个简短的解析脚本作为练习.我的大部分脚本都包含纯函数,但我有两个嵌套的IO组件:

  1. 从路径中读取文件列表.
  2. 读取每个文件的内容,这些内容反过来将成为程序其余部分的输入.

我的工作原理,但嵌套的IO和fmap图层"感觉"笨重,就像我应该避免嵌套IO(不知何故),或者更巧妙地使用do notation来避免所有的fmaps.我想知道我是否过于复杂,做错了等等.以下是一些相关的代码:

getPaths :: FilePath -> IO [String]
getPaths folder = do
    allFiles <- listDirectory folder
    let txtFiles = filter (isInfixOf ".txt") allFiles
        paths = map ((folder ++ "/") ++) txtFiles
    return paths

getConfig :: FilePath -> IO [String]
getConfig path = do
    config <- readFile path
    return $ lines config

main = do
    paths = getPaths "./configs"
    let flatConfigs = map getConfigs paths
        blockConfigs = map (fmap chunk) flatConfigs
    -- Parse and do stuff with config data.
    return
Run Code Online (Sandbox Code Playgroud)

我最终处理IO [IO String]使用listDirectory作为readFile的输入.不是难以管理的,但是如果我使用do notation来解开[IO String]发送到一些解析器函数,我仍然最终使用嵌套fmap或污染我所谓的具有IO感知的纯函数(fmap等).后者似乎更糟糕,所以我正在做前者.例:

type Block = [String]
getTrunkBlocks :: [Block] -> [Block]
getTrunkBlocks = filter (liftM2 (&&) isInterface isMatchingInt)
    where isMatchingInt line = isJust $ find predicate line
          predicate = isInfixOf "switchport mode trunk"

main = do
    paths <- getPaths "./configs"
    let flatConfigs = map getConfig paths
        blockConfigs = map (fmap chunk) flatConfigs
        trunks = fmap (fmap getTrunkBlocks) blockConfigs
    return $ "Trunk count: " ++ show (length trunks)
Run Code Online (Sandbox Code Playgroud)

fmap,fmap,fmap ...我觉得我无意中使这个变得比必要的更复杂,并且无法想象如果我有更深的IO嵌套,这会变得多么复杂.

建议?

提前致谢.

use*_*560 8

我想你想要这样的东西main:

main = do
    paths <- getPaths "./configs"
    flatConfigs <- traverse getConfig paths
    let blockConfigs = fmap chunk flatConfigs
    -- Parse and do stuff with config data.
    return ()
Run Code Online (Sandbox Code Playgroud)

相比

fmap :: Functor f => (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
Run Code Online (Sandbox Code Playgroud)

它们非常相似,但traverse让你使用像IO.

以下是再次专门用于比较的类型:

fmap     :: (a -> b)    -> [a] -> [b]
traverse :: (a -> IO b) -> [a] -> IO [b]
Run Code Online (Sandbox Code Playgroud)

(traverse也称为mapM)


Tar*_*ren 5

你对'嵌套'的看法实际上是对monad是什么的非常好的洞察力.Monads可以看作Functors,有两个额外的操作,返回类型a -> m a和连接类型m (m a) -> m a.然后我们可以创建a -> m b可组合类型的函数:

fmap :: (a -> m b) -> m a -> m (m b)
f =<< v = join (fmap f v) :: (a -> m b) -> m a -> m b
Run Code Online (Sandbox Code Playgroud)

所以我们想在这里使用join,但m [m a]目前我们的monad组合器不会直接帮助.让我们搜索m [m a] -> m (m [a])使用hoogle,我们的第一个结果看起来很有希望.是的sequence:: [m a] -> m [a].
如果我们查看相关函数,我们也会发现traverse :: (a -> IO b) -> [a] -> IO [b]它们是相似的sequence (fmap f v).

有了这些知识,我们可以写:

readConfigFiles path = traverse getConfig =<< getPaths path
Run Code Online (Sandbox Code Playgroud)