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

时间:2017-03-02 22:44:31

标签: haskell

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

  1. 从路径中读取文件列表。
  2. 阅读每个文件的内容,而这些内容又是该程序其余部分的输入。
  3. 我有什么作品,但是嵌套的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
    

    我最终使用listDirectory作为readFile的输入来处理IO [IO String]。不是难以管理,但是如果我使用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)
    

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

    建议?

    提前致谢。

2 个答案:

答案 0 :(得分:8)

我认为你想要main

这样的东西
main = do
    paths <- getPaths "./configs"
    flatConfigs <- traverse getConfig paths
    let blockConfigs = fmap chunk flatConfigs
    -- Parse and do stuff with config data.
    return ()

比较

fmap :: Functor f => (a -> b) -> f a -> f b

traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)

它们非常相似,但traverse允许您使用IO等效果。

这些类型再次专门用于比较:

fmap     :: (a -> b)    -> [a] -> [b]
traverse :: (a -> IO b) -> [a] -> IO [b]

traverse也称为mapM

答案 1 :(得分:5)

你对“嵌套”的想法&#39;实际上是一个非常好的洞察monad是什么。可以将Monads视为具有两个附加操作的Functors,返回类型为const start_time = "2017-03-02T15:57:00Z"; const stop_time = "2017-03-02T17:51:00Z"; const local_timestamp = 1488498242256; // Thu Mar 02 2017 16:44:02 GMT-0700 (MST) const start = moment(start_time).valueOf(); // 1488470220000 const stop = moment(stop_time).valueOf(); // 1488477060000 const is_between = local_timestamp >= start && local_timestamp <= stop; // false 并加入类型const start = moment(start_time).utc().valueOf(); // 1488470220000 const stop = moment(stop_time).utc().valueOf(); // 1488477060000 const is_between = local_timestamp >= start && local_timestamp <= stop; // false 。然后我们可以创建类型为const now = moment(local_timestamp); const isBetween = now.isBetween(start, stop); // false 的函数:

a -> m a

所以我们想在这里使用join,但目前只有m (m a) -> m a,所以我们的monad组合器不会直接帮助。让我们使用hoogle搜索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 如果我们查看相关函数,我们也会找到类似m [m a]的{​​{1}}。

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

m [m a] -> m (m [a])