如何为箭头定义滤镜功能?

时间:2011-07-19 00:50:21

标签: haskell arrows

我正在阅读John Hughes的论文Programming with Arrows 我已经在第2.5节第20页的第一次练习中难过了。

我们可以使用ArrowArrowChoice类型类,以及函数实例,流函数[a] -> [b]和monadic函数a -> m b通过{{1输入。

给出了一个Kleisli示例:

mapA

这是一次尝试:

mapA f = arr listcase >>>
         arr (const []) ||| (f *** mapA >>> arr (uncurry (:)))

这种徒劳的尝试背后的(暴力)理由如下: 使用listcase :: [a] -> (Either () (a,[a])) listcase [] = Left () listcase (x:xs) = Right (x,xs) helper :: (Bool,a) -> [a] -> Either (a,[a]) [a] helper (True,x) y = Left (x,y) helper (False,x) y = Right y test :: Arrow a => (b -> Bool) -> a (b,c) ((Bool,b), c) test p = first (arr p &&& arr id) filterA :: Arrow a => (b -> Bool) -> a [b] [c] filterA p = f >>> (g ||| (h >>> (j ||| (filterA p)))) where f = arr listcase g = arr (const []) h = test p >>> (uncurry helper) j = (arr id *** (filterA p)) >>> (arr (uncurry (:))) 有两种选择:filterA类似listcase,以及应用谓词map的结果。它从p开始,检查列表并使用map返回Either值。如果列出了空列表listcase,则g右侧的所有内容都会应用于|||类型的值,其中包含(a,[a])和{{1 }} 分别。首先应用head函数,该函数在保留tail的同时应用谓词,返回类型h的值。这将传递给head((Bool, head),tail)决定是否保留(uncurry helper),具体取决于head值。它将结果作为Bool值返回,以便我们可以将选择方法Either应用于它。此值将传递给下一个选项:(|||),以便如果谓词保持(j ||| (filterA p)),则True将应用于包含jhead的对。当tail应用于head时,id会使用filter p进行过滤。两个结果都作为一对返回。然后使用tail arr (uncurry (:))对此对进行协调。否则,map将单独传递给tail

我怀疑它和我要做的一样困难,我想我错过了一些非常明显的东西。

2 个答案:

答案 0 :(得分:3)

抱歉,我不太关注你的逻辑,但让我们看看非箭头代码的作用。它

  • 检查列表是否为空,如果是,则返回
  • 否则,请关闭列表的头部,调用元素x
  • 对列表的其余部分进行递归,调用结果ys
  • 如果谓词p在头部为真,那么我们会将x追加到ys
  • 否则,我们会返回ys

listcase函数[实现前2个任务]看起来很不错,但请记住您正在返回一个列表,所以不妨返回{而不是unit并重新映射{{1} }。

你有第二个子弹的递归代码埋在最后两个案例中,而我直接暴露它,但没关系。

对于最后一次合并,你用const []编写它,但由于你不需要在你的目标类别中组成任何其他箭头,你也可以只提起一个函数来完成所有的工作。在我的下面的代码中,那是|||

rejoin

当然,使用filterA :: forall arr a. ArrowChoice arr => arr a Bool -> arr [a] [a] filterA p = arr lstcase >>> (filterRest ||| id) where -- left if has a head, right otherwise lstcase :: [a] -> (Either (a, [a]) [a]) lstcase (x:xs) = Left (x, xs) lstcase [] = Right [] -- if we got a head, then -- for the head ("first" in code), map this to itself and p's result on it -- recurse on the rest of the list ("second" part) -- append the element to the recursion result if p's result is true filterRest :: arr (a, [a]) [a] filterRest = (first (id &&& p) >>> second (filterA p) >>> arr rejoin) rejoin :: ((a, Bool), [a]) -> [a] rejoin ((x, True), rest) = x:rest rejoin ((x, False), rest) = rest ***&&&|||等来清楚表达您的想法需要时间。

有点批评。

  • 确保您正在实施他们要求的功能!如果您只使用原始函数first,那么您也可以声明p。你真的想要举箭filterA = arr filter。也就是说,更改只是键入p而不是p,因此您的代码有正确的想法。
  • arr p不是箭头空间中的东西,它只是一个原始函数。

在开发这些东西时,我通常会写一个骨架,并声明类型。这有助于我弄清楚发生了什么。例如,我从

开始
(uncurry helper)

但是,当您在filterA :: ArrowChoice arr => arr a Bool -> arr [a] [a] filterA p = arr lstcase >>> (filterRest ||| id) where -- left if has a head, right otherwise lstcase :: [a] -> (Either (a, [a]) [a]) lstcase = undefined filterRest :: arr (a, [a]) [a] filterRest = undefined 声明中添加fa时,您需要告诉filterRest arrfilterRest的{​​{1}}相同(键入变量作用域),因此请使用上面的filterA

答案 1 :(得分:2)

这是一个小小的简化:

test :: Arrow a => (b -> Bool) -> a b ([b] -> [b])
test p = arr $ \b -> if p b then (b:) else id
         
filterA :: Arrow a => (b -> Bool) -> a [b] [b]
filterA p = f >>> (g ||| h)
  where f = arr listcase
        g = arr id
        h = (test p *** filterA p) >>> (arr (uncurry ($)))

filterA' :: Arrow a => a b Bool -> a [b] [b]
filterA' p = f >>> (g ||| h)
  where f = arr listcase
        g = arr id
        h = (i *** filterA p) >>> (arr (uncurry ($)))
        i = proc x -> do 
              b <- p -< x
              returnA -< if b then (x:) else id