我正在阅读John Hughes的论文Programming with Arrows 我已经在第2.5节第20页的第一次练习中难过了。
我们可以使用Arrow
和ArrowChoice
类型类,以及函数实例,流函数[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
将应用于包含j
和head
的对。当tail
应用于head
时,id
会使用filter p
进行过滤。两个结果都作为一对返回。然后使用tail
arr (uncurry (:))
对此对进行协调。否则,map
将单独传递给tail
。
我怀疑它和我要做的一样困难,我想我错过了一些非常明显的东西。
答案 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
arr
与filterRest
的{{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