2

很难给这个问题一个好的标题……我又被 HXT 困住了。我明白我想要做什么,但我不知道如何让它与箭头很好地配合。在这里,我对问题进行了简单的描述。

函数foo接受一个Int并返回一个箭头:

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

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

bar :: ArrowXml a => a XmlTree String

现在,我需要编写一个从s 到sbaz的映射并返回一个箭头:StringInt

import qualified Data.Map.Lazy as M

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

逻辑baz:提取属性值bar并在地图中查找。如果M.lookup返回Just x,调用foo x,否则不执行任何操作(箭头的输入不变)。

AFAIK 每个这样的箭头都用作过滤器,因此实际上ArrowXml a => a XmlTree String类型意味着它需要一个XmlTree并返回(可能为空的)Strings 列表。这让我重新制定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

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

4

2 回答 2

2

如果你重新格式化你的类型签名,你可以让它排列得很好。由于您有来自箭头结果的值来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

然后 for baz,它应该期望 anXmlTreeString输入 from bar,所以它的箭头类型需要是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 Int, XmlTree)输入(Int, XmlTree)当且仅当Maybe Int是一个Just something. 这是箭头语法真正派上用场的地方。由于我们在这里做出决定,它还要求我们的箭头是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约束,ArrowXml范围内的所有实例Text.XML.HXT.Core也都从 implementation引入ArrowChoice

如果您对没有proc符号的情况感到好奇,即使是这个简单的案例陈述也会变成(我认为)

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符号应该保持相对简单。

于 2015-04-22T19:42:26.050 回答
1

我花了一些时间来了解如何实现这一点,因为当您的箭头具有 like 类型时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))

所有的魔法都是因为 ($<) 功能而发生的。从文档中:

使用输入中的额外参数计算箭头的参数,并将所有参数值的箭头应用于输入

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

于 2015-05-02T08:19:01.990 回答