Haskell-管道:如何使用 drawAll 来测试具有 MonadSafe 约束的生产者?
Haskell-pipes: how to use drawAll to test a producer with a MonadSafe constraint?
我有一个生产者,给定一个路径,它遍历文件系统产生 Haskell 个文件的路径。它建立在管道文件之上:
import Pipes
import Pipes.Files
import Pipes.Safe
import qualified Pipes.Prelude as P
import Data.Monoid ((<>))
import Data.List (isSuffixOf)
import System.Directory (doesFileExist)
-- | Starting from a path, generate a sequence of paths corresponding
-- to Haskell files. The fileystem is traversed depth-first.
allFiles :: (MonadIO m, MonadSafe m) => FilePath -> IO (Producer FilePath m ())
allFiles path = do
isFile <- doesFileExist path
if isFile then return $ each [path] >-> P.filter (".hs" `isSuffixOf`)
else return $ find path (glob "*.hs" <> regular)
现在我想用 Hspec 对其进行测试,但我发现很难将生产者转换为列表。如果没有导致大量类型错误的 MonadSafe m
约束,它会更简单。这是我写的:
import Pipes
import Pipes.Safe
import Pipes.Parse
import Test.Hspec
shouldReturnP :: (MonadIO m, MonadSafe m)
=> IO (Producer FilePath m ()) -> [FilePath] -> Expectation
shouldReturnP action res = do
prod <- action
let paths = runSafeT $ evalStateT drawAll prod
paths `shouldBe` res
应该这样使用:
spec :: Spec
spec = do
describe "allFiles" $
it "traverses the filesystem depth-first returning only hs files" $
allFiles ("test" </> "tree") `shouldReturnP`
[ "test" </> "tree" </> "a.hs"
, "test" </> "tree" </> "sub" </> "b.hs"
, "test" </> "tree" </> "sub" </> "c.hs"
, "test" </> "tree" </> "sub2" </> "a.hs"
, "test" </> "tree" </> "sub2" </> "e.hs"
]
编译测试出现以下错误:
test/Spec.hs:57:47:
Couldn't match type ‘m’ with ‘Pipes.Safe.SafeT []’
‘m’ is a rigid type variable bound by
the type signature for
shouldReturnP :: (MonadIO m, MonadSafe m) =>
IO (Producer FilePath m ()) -> [FilePath] -> Expectation
at test/Spec.hs:53:18
Expected type: Producer FilePath (Pipes.Safe.SafeT []) ()
Actual type: Producer FilePath m ()
Relevant bindings include
prod :: Producer FilePath m () (bound at test/Spec.hs:56:5)
action :: IO (Producer FilePath m ())
(bound at test/Spec.hs:55:15)
shouldReturnP :: IO (Producer FilePath m ())
-> [FilePath] -> Expectation
(bound at test/Spec.hs:55:1)
In the second argument of ‘evalStateT’, namely ‘prod’
In the second argument of ‘($)’, namely ‘evalStateT drawAll prod’
test/Spec.hs:58:22:
Couldn't match type ‘Char’ with ‘[Char]’
Expected type: [[FilePath]]
Actual type: [FilePath]
In the second argument of ‘shouldBe’, namely ‘res’
In a stmt of a 'do' block: paths res
如何使用 Pipes.Prelude 中的 toListM
:
...
import qualified Pipes.Prelude as P
...
files1 :: (MonadIO m, MonadSafe m) => Producer FilePath m ()
files1 = find "." (glob "*.hs" <> regular)
test1 = do
got <- runSafeT $ runEffect $ P.toListM files1
shouldBe got ["a.hs", "b.hs", "c.hs"]
-- using `allFiles`:
test2 = do
prod <- allFiles "."
got <- runSafeT $ runEffect $ P.toListM prod
shouldBe got ["a.hs", "b.hs"]
要编写 shouldReturnP
函数,请从以下开始:
shouldReturnP1 prod expected = do
let _ = expected :: [FilePath]
got <- runSafeT $ P.toListM prod
shouldBe got expected
并让 ghc 告诉您类型是什么,即:
shouldReturnP1
:: (Eq a, Show a) => Producer a (SafeT IO) () -> [a] -> IO ()
您可以使用以下方法进行测试:
testP1 = shouldReturnP1 files1 ["a.hs", "b.hs", "c.hs"]
对于 IO-action 版本,写:
shouldReturnP2 action expected = do
let _ = expected :: [FilePath]
prod <- action
paths <- runSafeT $ runEffect $ P.toListM prod
paths `shouldBe` expected
并且 ghc 告诉你类型是:
shouldReturnP2
:: IO (Producer FilePath (SafeT IO) ()) -> [FilePath] -> IO ()
和一个测试:
testP2 = shouldReturnP2 (allfiles ".") ["a1.hs"]
更新
根据评论中关于将 doesFileExist
检查放入管道的讨论:
allfiles2 :: MonadSafe m => FilePath -> Producer FilePath m ()
allfiles2 path = do
exists <- liftIO $ doesFileExist path
if exists
then each [path] >-> P.filter (".hs" `isSuffixOf`)
else find path (glob "*.hs" <> regular)
我有一个生产者,给定一个路径,它遍历文件系统产生 Haskell 个文件的路径。它建立在管道文件之上:
import Pipes
import Pipes.Files
import Pipes.Safe
import qualified Pipes.Prelude as P
import Data.Monoid ((<>))
import Data.List (isSuffixOf)
import System.Directory (doesFileExist)
-- | Starting from a path, generate a sequence of paths corresponding
-- to Haskell files. The fileystem is traversed depth-first.
allFiles :: (MonadIO m, MonadSafe m) => FilePath -> IO (Producer FilePath m ())
allFiles path = do
isFile <- doesFileExist path
if isFile then return $ each [path] >-> P.filter (".hs" `isSuffixOf`)
else return $ find path (glob "*.hs" <> regular)
现在我想用 Hspec 对其进行测试,但我发现很难将生产者转换为列表。如果没有导致大量类型错误的 MonadSafe m
约束,它会更简单。这是我写的:
import Pipes
import Pipes.Safe
import Pipes.Parse
import Test.Hspec
shouldReturnP :: (MonadIO m, MonadSafe m)
=> IO (Producer FilePath m ()) -> [FilePath] -> Expectation
shouldReturnP action res = do
prod <- action
let paths = runSafeT $ evalStateT drawAll prod
paths `shouldBe` res
应该这样使用:
spec :: Spec
spec = do
describe "allFiles" $
it "traverses the filesystem depth-first returning only hs files" $
allFiles ("test" </> "tree") `shouldReturnP`
[ "test" </> "tree" </> "a.hs"
, "test" </> "tree" </> "sub" </> "b.hs"
, "test" </> "tree" </> "sub" </> "c.hs"
, "test" </> "tree" </> "sub2" </> "a.hs"
, "test" </> "tree" </> "sub2" </> "e.hs"
]
编译测试出现以下错误:
test/Spec.hs:57:47:
Couldn't match type ‘m’ with ‘Pipes.Safe.SafeT []’
‘m’ is a rigid type variable bound by
the type signature for
shouldReturnP :: (MonadIO m, MonadSafe m) =>
IO (Producer FilePath m ()) -> [FilePath] -> Expectation
at test/Spec.hs:53:18
Expected type: Producer FilePath (Pipes.Safe.SafeT []) ()
Actual type: Producer FilePath m ()
Relevant bindings include
prod :: Producer FilePath m () (bound at test/Spec.hs:56:5)
action :: IO (Producer FilePath m ())
(bound at test/Spec.hs:55:15)
shouldReturnP :: IO (Producer FilePath m ())
-> [FilePath] -> Expectation
(bound at test/Spec.hs:55:1)
In the second argument of ‘evalStateT’, namely ‘prod’
In the second argument of ‘($)’, namely ‘evalStateT drawAll prod’
test/Spec.hs:58:22:
Couldn't match type ‘Char’ with ‘[Char]’
Expected type: [[FilePath]]
Actual type: [FilePath]
In the second argument of ‘shouldBe’, namely ‘res’
In a stmt of a 'do' block: paths res
如何使用 Pipes.Prelude 中的 toListM
:
...
import qualified Pipes.Prelude as P
...
files1 :: (MonadIO m, MonadSafe m) => Producer FilePath m ()
files1 = find "." (glob "*.hs" <> regular)
test1 = do
got <- runSafeT $ runEffect $ P.toListM files1
shouldBe got ["a.hs", "b.hs", "c.hs"]
-- using `allFiles`:
test2 = do
prod <- allFiles "."
got <- runSafeT $ runEffect $ P.toListM prod
shouldBe got ["a.hs", "b.hs"]
要编写 shouldReturnP
函数,请从以下开始:
shouldReturnP1 prod expected = do
let _ = expected :: [FilePath]
got <- runSafeT $ P.toListM prod
shouldBe got expected
并让 ghc 告诉您类型是什么,即:
shouldReturnP1
:: (Eq a, Show a) => Producer a (SafeT IO) () -> [a] -> IO ()
您可以使用以下方法进行测试:
testP1 = shouldReturnP1 files1 ["a.hs", "b.hs", "c.hs"]
对于 IO-action 版本,写:
shouldReturnP2 action expected = do
let _ = expected :: [FilePath]
prod <- action
paths <- runSafeT $ runEffect $ P.toListM prod
paths `shouldBe` expected
并且 ghc 告诉你类型是:
shouldReturnP2
:: IO (Producer FilePath (SafeT IO) ()) -> [FilePath] -> IO ()
和一个测试:
testP2 = shouldReturnP2 (allfiles ".") ["a1.hs"]
更新
根据评论中关于将 doesFileExist
检查放入管道的讨论:
allfiles2 :: MonadSafe m => FilePath -> Producer FilePath m ()
allfiles2 path = do
exists <- liftIO $ doesFileExist path
if exists
then each [path] >-> P.filter (".hs" `isSuffixOf`)
else find path (glob "*.hs" <> regular)