Как преобразовать функцию, возвращающую [] в Traversable? - PullRequest
4 голосов
/ 01 июня 2019

У меня есть следующий модуль, который реализует просмотр каталога:

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 ]

5 голосов
/ 01 июня 2019

Я думаю, что это не так на :?

Действительно, это так. Если вы используете (:) для построения структуры, структура будет списком, и вы не сможете изменить тип walk, чтобы утверждать, что она возвращает произвольную структуру, которую можно пройти. Не существует действительно хорошего Traversable -центрического обходного пути, либо: Traversable означает, что через суперкласс Foldable у вас есть toList, но не fromList.

3 голосов
/ 01 июня 2019

Полиморфное создание списков и разработка классов для полиморфных контейнеров в целом оказалось более сложным, чем это может показаться на первый взгляд. Текущее решение 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
...