如何将返回[]的函数转换为Traversable?

时间:2019-06-01 15:13:36

标签: list haskell traversable

我有以下实现目录漫游的模块:

module Walk
  ( walk
  ) where

import           Control.Monad
import           Control.Monad.IO.Class
import           Data.List
import           System.Directory
import           System.FilePath

walk :: (MonadIO m) => FilePath -> m [(FilePath, [FilePath])]
walk root = do
  entries <- liftIO $ listDirectory root
  (files, dirs) <- partition snd <$> liftM2 (<$>) zip (mapM (liftIO . doesFileExist . (root </>))) entries
  ((root, map fst files) :) . concat <$> mapM (walk . (root </>) . fst) dirs

它当前返回一个列表,但我希望它返回一个Traversable

walk :: (MonadIO m, Traversable t) => FilePath -> m (t (FilePath, [FilePath]))

如果更改签名,则会出现以下错误:

    • Couldn't match type ‘t’ with ‘[]’
      ‘t’ is a rigid type variable bound by
        the type signature for:
          walk :: forall (m :: * -> *) (t :: * -> *).
                  (MonadIO m, Traversable t) =>
                  FilePath -> m (t (FilePath, [FilePath]))
      Expected type: m (t (FilePath, [FilePath]))
        Actual type: m [(FilePath, [FilePath])]
    • In a stmt of a 'do' block:
        ((root, map fst files) :) . concat
          <$> mapM (walk . (root </>) . fst) dirs
      In the expression:
        do entries <- liftIO $ listDirectory root
           (files, dirs) <- partition snd
                              <$>
                                liftM2
                                  (<$>) zip (mapM (liftIO . doesFileExist .
(root </>))) entries
           ((root, map fst files) :) . concat
             <$> mapM (walk . (root </>) . fst) dirs
      In an equation for ‘walk’:
          walk root
            = do entries <- liftIO $ listDirectory root
                 (files, dirs) <- partition snd
                                    <$>
                                      liftM2
                                        (<$>)
                                        zip
                                        (mapM (liftIO . doesFileExist .
(root </>)))
                                        entries
                 ((root, map fst files) :) . concat
                   <$> mapM (walk . (root </>) . fst) dirs
    • Relevant bindings include
        walk :: FilePath -> m (t (FilePath, [FilePath]))

我认为:失败了吗?我不能确定我该如何解决?

2 个答案:

答案 0 :(得分:5)

  

我认为:失败了吗?

的确如此。如果使用(:)来构建结构,则该结构将是一个列表,并且您无法更改walk的类型来声明它返回任意可遍历的结构。也没有一个很好的以Traversable为中心的解决方法,或者:Traversable意味着您通过其Foldable超类拥有一个toList,但没有一个{{1} }。

答案 1 :(得分:2)

列表的多态生成以及通常为多态容器设计类已被证明比最初看起来要困难得多。 GHC目前用于生产完全多态容器的解决方案是Traversable类,而不是仅在诸如IsList这样的现有容器上运行。

GHC.Exts中定义为:

class IsList l where
  type Item l
  fromList  :: [Item l] -> l
  ...

已经有列表,非空列表,地图和大多数其他类型的实例,这些实例来自您认为是标准的Haskell库。

请注意,类型参数l的类型为*,而不是容器* -> *的类型参数。您提供了完全应用的类型,并且可以根据需要用类型相等性约束Item l类型。例如:

{-# LANGUAGE TypeFamilies #-}
module Walk
  ( walk
  ) where

import           Control.Monad
import           Control.Monad.IO.Class
import           Data.List
import           System.Directory
import           System.FilePath
import           GHC.Exts

walk :: (IsList l, Item l ~ (FilePath,[FilePath]), MonadIO m) => FilePath -> m l
walk root =
  do entries <- liftIO $ listDirectory root
     (files, dirs) <- partition snd <$> liftM2 (<$>) zip (mapM (liftIO . doesFileExist . (root </>))) entries
     fromList . ((root, map fst files) :) . concat <$> mapM (walk . (root </>) . fst) dirs