HXT:如何使用箭头的输出作为函数参数?

HXT: How to use output of an arrow as function argument?

很难给这个问题起个好标题……我被 HXT 困住了 再次。我明白我想做什么,但我不确定如何让它发挥作用 很好用箭头。这里我简单的描述一下问题。

函数 foo 接受一个 Int 和 returns 一个箭头:

foo :: ArrowXml a => Int -> a XmlTree XmlTree

函数bar提取某些属性的值:

bar :: ArrowXml a => a XmlTree String

现在,我需要编写 baz 获取从 Strings 到 Ints 的映射,并且 returns一个箭头:

import qualified Data.Map.Lazy as M

baz :: ArrowXml a => M.Map String Int -> a XmlTree XmlTree

baz的逻辑:提取bar属性的值并在 地图。如果M.lookup returns Just x,调用foo x,否则不做 任何东西(箭头的输入不变)。

AFAIK 每个这样的箭头都用作过滤器,所以实际上 ArrowXml a => a XmlTree String 类型意味着它需要 XmlTree 和 returns(可能 空)String 列表。这让我重新表述 baz 的逻辑。为了 给定输入 XmlTree 可能有很多字符串,应该使用每个字符串 查找一个整数,第一个找到的整数应该传递给 foo。如果 所有这些都会导致 Nothing,什么都不做。

这是我想出的:

baz :: ArrowXml a => M.Map String Int -> a XmlTree XmlTree
baz m = this &&& (bar >>> arr (`M.lookup` m)) >>> arr (uncurry f)
    where f xml Nothing  = xml
          f xml (Just x) = foo x xml
-- compiler says:          ^^^ not so fast, boy

Could not deduce (ArrowXml (->)) arising from a use of ‘foo’
from the context (ArrowXml a)
  bound by the type signature for
             baz :: ArrowXml a => M.Map String Int -> a XmlTree XmlTree

不仅编译器不喜欢它,而且也很难推理。

如果你稍微重新制定你的类型签名,你可以让它排列得很好。由于您的值来自 baz 中的箭头影响 foo 行为的结果,因此需要使用箭头而不是作为典型参数将这些值提供给 foo。这实际上简化了很多事情,但我建议创建一个 foo,然后创建一个 fooWrapper 来处理决策本身。使用正确的类型,您将拥有

{-# LANGUAGE Arrows, NoMonomorphismRestriction #-}

import qualified Data.Map as M
import Control.Arrow.ArrowTree
import Text.XML.HXT.Core

foo :: ArrowXml a => a (Int, XmlTree) XmlTree
foo = undefined

bar :: ArrowXml a => a XmlTree String
bar = undefined

然后对于 baz,它应该期待来自 barXmlTreeString 输入,所以它的箭头类型需要是 a (String, XmlTree) something,在这里我发现最简单的实现方式是

baz :: ArrowXml a => M.Map String Int -> a (String, XmlTree) (Maybe Int, XmlTree)
baz m = first $ arr $ flip M.lookup m

这个箭头所做的就是将 String 转换为对传入的 M.Map 的查找(假设这已经在一般环境中给出)。然后,当且仅当 Maybe IntJust something 时,我们需要一个包装器将 (Maybe Int, XmlTree) 送入 (Int, XmlTree)。这是箭头语法真正派上用场的地方。由于我们在这里做出决定,因此还要求我们的箭头是 ArrowChoice,因此

fooWrapper :: (ArrowXml a, ArrowChoice a) => a (Maybe Int, XmlTree) XmlTree
fooWrapper = proc (lkup, tree) -> do
    case lkup of
        Nothing -> returnA -< tree
        Just v  -> foo -< (v, tree)

现在我们只需使用内置组合器就可以将所有内容组合成一个整体应用程序(我还发现了 returnA = arr id,所以您可以改用它,我只是认为它更容易理解arr id)

program :: (ArrowXml a, ArrowChoice a) => M.Map String Int -> a XmlTree XmlTree
program m =
    bar &&& arr id >>> -- First split the input between bar and arr id
    baz m          >>> -- Feed this into baz m
    fooWrapper         -- Feed the lookup into fooWrapper so it can make the
                       -- decision on how to route the XmlTree

您无需担心 ArrowChoice 约束,所有从 Text.XML.HXT.Core 引入范围的 ArrowXml 实例也实现了 ArrowChoice.

如果您想知道如果没有 proc 符号会是什么样子,即使这个简单的 case 语句也会变成(我认为)

fooWrapper :: (ArrowXml a, ArrowChoice a) => a (Maybe Int, XmlTree) XmlTree
fooWrapper =
    arr (\(lkup, tree) -> case lkup of
        Nothing -> Left tree
        Just v  -> Right (v, tree)) >>>
    (returnA ||| foo)

||| 的使用迫使它实施 ArrowChoice。虽然这还不算太糟糕,但我不会完全称其为可读的,而且发生的事情太多与实际的业务逻辑没有任何关系。一旦你转向更复杂的情况,它的复杂性也会爆炸式增长,而 proc 符号应该保持相对简单。

我花了一些时间才明白如何做到这一点,因为当你 箭头的类型类似于 a (b, XmlTree) XmlTree,你真的可以使用它,因为 类型与 API.

的其余部分冲突

这是另一个似乎更惯用的解决方案:

baz :: ArrowXml a => M.Map String Int -> a XmlTree XmlTree
baz m = maybe this foo $< (bar >>> arr (`M.lookup` m))

所有的奇迹都是因为 ($<) 功能。来自文档:

compute the parameter for an arrow with extra parameters from the input and apply the arrow for all parameter values to the input

另请参阅 HXT Wiki 的这一部分:8.2 将外部引用转换为 绝对 参考文献.