【问题标题】:Haskell Control.Arrow: trying to write a filterA functionHaskell Control.Arrow:尝试编写一个 filterA 函数
【发布时间】:2015-07-04 01:33:26
【问题描述】:

我正在尝试编写一个filterA :: (ArrowChoice arr) => arr a Bool -> arr [a] [a] 函数,该函数从f :: arr a Bool 返回False 的列表中删除每个元素。这就是我目前所拥有的

listcase [] = Left ()
listcase (x:xs) = Right (x, xs)

filterA f = arr listcase >>>
            arr (const []) ||| (first (f &&& arr id) >>>
            arr (\((b,x),xs) -> if b then
                x : (filterA f xs)
                else filterA f xs
            ))

现在使用 (->) a 箭头测试它时可以使用,如下所示:

λ> filterA (== 8) [8,9]
[8]

但是,对于像 Kleisli Arrows 这样的人来说,它不起作用

λ> runKleisli (Kleisli $ filterA (== 8)) (return [8,9] :: [IO Int])

<interactive>:160:47:
    Couldn't match expected type `IO Int' with actual type `[t0]'
    In the first argument of `return', namely `[8, 9]'
    In the second argument of `runKleisli', namely
      `(return [8, 9] :: [IO Int])'
    In the expression:
      runKleisli (Kleisli $ filterA (== 8)) (return [8, 9] :: [IO Int])

并且在添加类型签名filterA :: (Arrow arr) =&gt; arr a Bool -&gt; arr [a] [a]filterA :: (ArrowChoice arr) =&gt; arr a Bool -&gt; arr [a] [a] 时,会抛出此错误:

arrows.hs:11:22:
    Could not deduce (arr ~ (->))
    from the context (Arrow arr)
      bound by the type signature for
                 filterA :: Arrow arr => arr a Bool -> arr [a] [a]
      at arrows.hs:7:12-51
      `arr' is a rigid type variable bound by
            the type signature for
              filterA :: Arrow arr => arr a Bool -> arr [a] [a]
            at arrows.hs:7:12
    Expected type: [a] -> [a]
      Actual type: arr [a] [a]
    The function `filterA' is applied to two arguments,
    but its type `arr a Bool -> arr [a] [a]' has only one
    In the second argument of `(:)', namely `(filterA f xs)'
    In the expression: x : (filterA f xs)

我不明白为什么。我错过了什么吗?

编辑: @jaket 的评论有效(我想这有点愚蠢),但类型签名仍然不匹配。 我还更新了代码以更紧凑(尽管仍然出现相同的错误)

filterA f = arr listcase >>>
            arr (const []) ||| (arr toEither >>>
            (filterA f) ||| (second (filterA f) >>> arr uncurry (:)))
  where toEither (x, xs) = if f x then Right (x, xs) else Left xs

顺便说一下,GHC 将类型推断为filterA :: (a -&gt; Bool) -&gt; [a] -&gt; [a]

【问题讨论】:

  • runKleisli (Kleisli $ filterA (== 8)) [8, 9]?
  • @jaket 修复了我猜的问题。但是,它不能修复类型签名的事情
  • 在您的更新中,您使用 f 就好像它是 if f x then ... 中的一个函数 - 使用 f 是 GHC 坚持仅在 arr 类型为事实上(-&gt;)。请参阅下面的答案。
  • 另外,请注意 @jaket 的 runKleisli (Kleisli $ filterA (== 8)) [8, 9] 版本不是您想要的 - 它绑定到 monad [],即使使用正确键入的 filterA

标签: haskell arrows


【解决方案1】:

您的问题是您尝试在使用arr 包装的函数定义内进行递归,并且您调用filterA f 就好像它是这一行中的一个函数:

                x : (filterA f xs)

仅当箭头类型为 (-&gt;) 时才有效,这是类型错误之一告诉您的内容。

相反,您需要在箭头级别进行递归,如下所示:

listcase :: [t] -> Either () (t, [t])
listcase [] = Left ()
listcase (x:xs) = Right (x, xs)

filterA :: (ArrowChoice arr) => arr a Bool -> arr [a] [a]
filterA f = listcase ^>>
            arr (const []) ||| ((f &&& arr id) *** filterA f >>^
                                (\((b, x), xs) -> if b then x:xs else xs))

(编译)

您的runKleisli 示例有点混乱,您的意思是:

runKleisli (filterA $ Kleisli $ return . (== 8)) [8,9]

runKleisli (filterA $ arr (== 8)) [8,9] :: IO [Int]

这直接来自查看类型。

【讨论】:

  • 谢谢,这行得通。我已经重写了函数以使用 toEither 像这样:filterA f = arr listcase &gt;&gt;&gt; arr (const []) ||| ((f &amp;&amp;&amp; arr id) *** filterA f &gt;&gt;^ toEither &gt;&gt;&gt; (arr id ||| (arr uncurry (:))))toEither ((True, x), xs) = Right (x, xs) toEither ((False, _), xs) = Left xs 并且我观察到一些我不完全理解的行为。使用&gt;&gt;^ toEither 的版本有效,而使用&gt;&gt;&gt; arr toEither 的版本无法编译。但是&gt;&gt;^不是定义为a &gt;&gt;^ f = a &gt;&gt;&gt; arr f吗?
  • 您的编译问题来自与您更改的内容相去甚远的其他事情,没有您认为的类型。您想要arr (uncurry (:))(arr $ uncurry (:)) - 也就是说,不要将arr 直接应用于uncurry。如果你这样做,那么&gt;&gt;&gt; arr toEither&gt;&gt;^ toEither 变体都将按预期编译
  • 如果您尝试使用通用箭头,因为 &gt;&gt;^&gt;&gt;&gt; 都关联到右侧(即,两者都是 infixr 1),您需要注意表达式a &gt;&gt;^ b &gt;&gt;&gt; c 的形式,即&gt;&gt;^ 右侧有任何&gt;&gt;&gt;,因为这意味着与a &gt;&gt;^ (b &gt;&gt;&gt; c) 相同,因此c 箭头将用于(-&gt;) 箭头实例,而不是整个函数所在的通用箭头。
【解决方案2】:

只是为了补充其他答案:使用Arrow syntax(另请参阅 GHC 手册,章节Arrow notation),您可以编写更具可读性的函数:

{-# LANGUAGE Arrows #-}

import Control.Arrow

filterA :: (ArrowChoice arr) => arr a Bool -> arr [a] [a]
filterA f = farr
  where
    farr = proc xs ->
            case xs of
                []       -> returnA -< []
                (x:xs')  -> do
                    b   <- f    -< x
                    ys' <- farr -< xs'
                    returnA -< if b then x : ys' else ys'

内部翻译成箭头符号的结果可能会不太简洁,但希望编译器会为您优化。

【讨论】:

    【解决方案3】:

    正如我在评论中提到的:

    runKleisli (Kleisli $ filterA (== 8)) [8, 9]
    

    接下来你需要将f :: a -&gt; b提升成箭头arr a b

    (first (arr f &&& arr id)
            ^^^
    

    在你的函数中:

    filterA :: ArrowChoice arr => (a -> Bool) -> arr [a] [a]
    filterA f = arr listcase >>>
                arr (const []) ||| (first (f &&& arr id) >>>
                arr (\((b,x),xs) -> if b then
                    x : (filterA f xs)
                    else filterA f xs
                ))
    

    【讨论】:

    • 但是根据我在帖子中提供的类型签名,f 已经是一个箭头。我该怎么办?
    • 哦等等。我明白你在说什么。给我一秒钟。
    猜你喜欢
    • 2020-06-23
    • 1970-01-01
    • 1970-01-01
    • 2019-08-31
    • 2016-08-13
    • 1970-01-01
    • 2020-04-07
    • 2013-12-03
    • 1970-01-01
    相关资源
    最近更新 更多