用于构建测试数据的monad

Mat*_*hid 6 monads haskell

好的,所以我正在尝试编写一个用于构建测试数据的monad,但我无法让它按照我想要的方式工作.它看起来像这样:

runBuildM :: [i] -> BuildM i o x -> [o]
-- Given a list of i, build a list of o.

source :: BuildM i o i
-- Fetch unique i.

yield :: o -> BuildM i o ()
-- Return a new o to the caller.

gather :: BuildM i o x -> BuildM i o o
-- Fetch every possible o from sub-computation.

local :: BuildM i o x -> BuildM i o x
-- Isolate any source invocations from the rest of the code.
Run Code Online (Sandbox Code Playgroud)

换句话说,它是一个供应单子,作家单子和列表单子.我的想法是我可以这样写:

build_tests depth = do
  local $ do
    v <- source
    yield v
    yield (map toLower v)
  yield "[]"
  yield "()"
  when (depth > 2) $ do
    t1 <- gather $ build_tests (depth-1)
    yield $ "(" ++ t1 ++ ")"
    yield $ "[" ++ t1 ++ "]"
    t2 <- gather $ build_tests (depth-1)
    yield $ "(" ++ t1 ++ "," ++ t2 ++ ")"
Run Code Online (Sandbox Code Playgroud)

我们的想法是生成所有可能的数据组合.你可以使用列表推导来做到这一点,但结果在语法上很糟糕.这是很多的可读性.不幸的是,它实际上并不起作用 ......

问题似乎归结为local功能不正常.该意向为任何source在子计算调用有它之外没有任何影响.(即,后续调用source从外面local块再次拿到的第一个标记.)然而,什么我执行local 实际上做的是将下一令牌一切(即包括子计算的内容).这显然是不正确的,但我不能为我的生活弯曲我的思想如何使其正常工作.

事实上,我在使代码按要求工作时遇到这个问题可能意味着我的monad的实际内部表示是错误的.任何人都可以正确地实施这个吗?


编辑:我或许应该意识到这一点,但我实际上没有指定我想要的预期结果.上面的代码应该产生这个:

["A", "a", "[]", "()", "(A)", "(a)", "[A]", "[a]", "(A, B)", "(A, b)", "(a, B)", "(a, b)"]
Run Code Online (Sandbox Code Playgroud)

结果恰好按此顺序出现并不是至关重要的.我希望单个案例出现在复合案例之前,但我并不太确定化合物出现的顺序.规则是相同的变量在任何单个表达式中都不会出现两次.

如果我们允许深度更深一些,我们还会得到诸如

"((A))", "([A])", "[(A)]", "((A, B), C)", "(A, (B, C))"
Run Code Online (Sandbox Code Playgroud)

等等.


它显然已被打破,但这是我到目前为止所拥有的:

newtype BuildM i o x = BuildM ([i] -> SEQ.Seq ([i], SEQ.Seq o, x))

instance Functor (BuildM i o) where
  fmap uf (BuildM sf) =
    BuildM $ \ is0 -> do
      (is1, os, x) <- sf is0
      return (is1, os, uf x)

instance Applicative (BuildM i o) where
  pure x = BuildM $ \ is0 -> return (is0, SEQ.empty, x)

  BuildM sf1 <*> BuildM sf2 =
    BuildM $ \ is1 -> do
      (is2, os2, f) <- sf1 is1
      (is3, os3, x) <- sf2 is2
      return (is3, os2 >< os3, f x)

instance Monad (BuildM i o) where
  return = pure

  BuildM sf1 >>= uf =
    BuildM $ \ is1 -> do
      (is2, os2, x) <- sf1 is1
      let BuildM sf2 = uf x
      (is3, os3, y) <- sf2 is2
      return (is3, os2 >< os3, y)

runBuildM :: [i] -> BuildM i o x -> [o]
runBuildM is0 (BuildM sf) =
  toList $ do
    (is, os, x) <- sf is0
    os

source :: BuildM i o i
source =
  BuildM $ \ is ->
    if null is
      then error "AHC.Tests.TestBuilder.source: end of input"
      else return (tail is, SEQ.empty, head is)

yield :: o -> BuildM i o ()
yield o = BuildM $ \ is -> return (is, SEQ.singleton o, () )

gather :: BuildM i o x -> BuildM i o' o
gather (BuildM sf1) =
  BuildM $ \ is1 -> do
    (is2, os2, _) <- sf1 is1
    o <- os2
    return (is2, SEQ.empty, o)

local :: BuildM i o x -> BuildM i o ()
local (BuildM sf1) =
  BuildM $ \ is1 ->
    let os = do (is2, os2, x) <- sf1 is1; os2
    in  return (is1, os, () )
Run Code Online (Sandbox Code Playgroud)

Cir*_*dec 2

您正在尝试重新发明管道和一些用于构建列表的好语法。这个问题比你如何描述它要简单得多。弦的来源可以与结构的构建完全分开。

您想要生成从某些来源绘制符号的结构。不用担心来源,让我们构建结构。每个结构都是一个Pipe将从某些源和yield字符串中提取并连接在一起以构建表达式的结构。

import Data.Char

import Data.Functor.Identity

import Pipes.Core
import Pipes ((>->))
import qualified Pipes as P
import qualified Pipes.Prelude as P

build_structures :: Int -> [Pipe String String Identity ()]
build_structures depth = gather $ do
    yield $ P.take 1
    yield $ P.map (map toLower) >-> P.take 1
    when (depth > 2) $ do
        t1 <- lift $ build_structures (depth - 1)
        yield $ P.yield "(" >> t1 >> P.yield ")"
        yield $ P.yield "[" >> t1 >> P.yield "]"
        t2 <- lift $ build_structures (depth - 1)
        yield $ P.yield "(" >> t1 >> P.yield "," >> t2 >> P.yield ")"
Run Code Online (Sandbox Code Playgroud)

这段代码使用了后续答案中的ContTyield技巧。

我们通过向其中提供符号并连接结果来运行其中一种结构。

run :: Pipe String String Identity () -> String
run p = concat . P.toList $ P.each symbols >-> p

-- an infinite source of unique symbols
symbols :: [String]
symbols = drop 1 symbols'
    where
        symbols' = [""] ++ do
            tail <- symbols'
            first <- ['A'..'Z']
            return (first : tail)
Run Code Online (Sandbox Code Playgroud)

这些示例生成所需的字符串。我将留下生成两个特殊情况"[]"和"()",它们不以递归术语出现,作为练习。

import Data.Functor

main = do
    putStrLn "Depth 2"
    print $ run <$> build_structures 2
    putStrLn "Depth 3"
    print $ run <$> build_structures 3
    putStrLn "Depth 4"
    print $ run <$> build_structures 4
Run Code Online (Sandbox Code Playgroud)

这导致

Depth 2
["A","a"]
Depth 3
["A","a","(A)","[A]","(A,B)","(A,b)","(a)","[a]","(a,B)","(a,b)"]
Depth 4
["A","a","(A)","[A]","(A,B)","(A,b)","(A,(B))","(A,[B])","(A,(B,C))","(A,(B,c))","(A,(b))","(A,[b])","(A,(b,C))","(A,(b,c))","(a)","[a]","(a,B)","(a,b)","(a,(B))","(a,[B])","(a,(B,C))","(a,(B,c))","(a,(b))","(a,[b])",...
Run Code Online (Sandbox Code Playgroud)