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

How to convert function returning [] to 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

它目前 return 是一个列表,但我希望它成为 return 一个 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]))

我认为它在 : 上失败了?我不能确定。我该如何解决这个问题?

I think it's failing on the :?

确实如此。如果您使用 (:) 构建结构,该结构将是一个列表,并且您无法更改 walk 的类型以将其声明为 returns 任意可遍历结构。也没有真正以 Traversable 为中心的解决方法:Traversable 意味着您通过其 Foldable 超类拥有 toList,但没有 fromList.

列表的多态生成以及通常为多态容器设计 classes 已被证明比最初看起来要困难得多。 GHC 当前用于生成完全多态容器的解决方案,与仅在 pre-existing 容器(例如 Traversable 上运行相比,是 IsList class.

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