haskell 透镜:合并子级值(xml-lens)

gblwokeq  于 2023-02-23  发布在  其他
关注(0)|答案(1)|浏览(145)

我有一个XML,看起来像这样:

<Root>
  <V>
    <A>AAA</A>
    <B>100</B>
    <C>...</C>
  </V>
  <V>
    <A>BBB</A>
    <B>200</B>
    <C>...</C>
  </V>
</Root>

我需要得到一个AB值的列表(例如元组列表):

[("AAA", 100), ("BBB", 200)]

使用xlm-lens方法,我知道如何获得一个字段列表:

As = doc ^.. root ... named "V" ... named "A" . text

问题是如何合并多个字段(在我的任务中-“A”和“B”)?

u3r8eeie

u3r8eeie1#

这不是一个容易得到支持的东西,因为它在几个方面是脆弱的。首先,如果两个光学元件重叠,那么将两个作用于相同值的光学元件组合在一起的操作就不能合法地进行。当然,这并不意味着它没有提供关于它如何违反透镜定律的注解。
第二,lensProduct本身是不够的。您还得到了一个隐含的假设,即每个<V>将包含恰好一个<A>和恰好一个<B>。一般来说,XML中没有这样的限制,因此xml-lens提供遍历而不是透镜。lensProduct不对遍历进行类型检查。这是一种便利,因为当没有一个匹配元素时,它甚至不清楚应该做什么。当然,你可以说"取笛卡尔积",但这可能会默默地违反更高层次的假设。事实上,我宁愿期待它很快就不符合最小惊奇定律。
不过,如果您愿意降低代码的安全性,lens提供了一个函数,可以将预期只有一个目标的遍历转换为镜头。
我没有安装xml-lens,但我相信这是一个足够的演示:

ghci> let x = (10, ["ab", "cd", "ef"])
ghci> x ^.. _2 . traverse . (lensProduct (singular $ ix 0) (singular $ ix 1))
[('a','b'),('c','d'),('e','f')]

ghci> let y = (10, ["ab", "cd", "ef", "g"])
ghci> y ^.. _2 . traverse . (lensProduct (singular $ ix 0) (singular $ ix 1))
[('a','b'),('c','d'),('e','f'),('g',*** Exception: singular: empty traversal
CallStack (from HasCallStack):
  error, called at src/Control/Lens/Traversal.hs:700:46 in lens-5.2-5baea74547b047869cdde9b7d52e4f741da162c6f70a1613022e059e88427e27:Control.Lens.Traversal
  singular, called at <interactive>:21:55 in interactive:Ghci15

请注意,如果singular要查找的值不存在,它将使程序崩溃。
这并不令人满意,但很难在lens试图工作的信心和清晰度的约束下做得更好。也许某种函数返回两次折叠的乘积会很有用,但它仍然会有清晰度问题,这将使它很难自信地工作。
(No我的第一个选择更长,但是为了完整性和作为另一个选项而保留。请参见下面的编辑。)不管怎样,我认为最好的解决方法是首先从文档中提取所有<V>元素,然后Map一个提取函数,并对每个元素进行显式的失败处理。此时,你可以决定是放弃失败还是将它们向上传播。有些东西并不适合lens的单遍范例。

    • 编辑:**

正如DanielWagner在评论中指出的,你可以做得更好一点,至少摆脱崩溃的情况:

ghci> let x = (10, ["ab", "cd", "ef"])
ghci> x ^.. _2 . traverse . (lensProduct (partsOf $ ix 0) (partsOf $ ix 1))
[("a","b"),("c","d"),("e","f")]

ghci> let y = (10, ["ab", "cd", "ef", "g"])
ghci> y ^.. _2 . traverse . (lensProduct (partsOf $ ix 0) (partsOf $ ix 1))
[("a","b"),("c","d"),("e","f"),("g","")]

您可以将singular替换为partsOf。这稍微改变了结果的类型。(注意,返回类型现在是[([Char],[Char])]而不是[(Char,Char)]。这将不再崩溃,并将处理意外数量的子元素的工作留给调用者。我喜欢它,因为它比使用singular要好得多。因为现在该类型至少警告您必须处理的潜在错误。这已经足够好了,我将优先使用它而不是前面的建议。

相关问题