我有lens指向json文档,例如
doc ^? ((key "body").values)
现在我想使用键"键"来索引body中的值,因为json看起来像
{"body": [{"key": 23, "data": [{"foo": 1}, {"foo": 2}]}]}
所以我正在寻找能够让我用另一个镜头进行索引的东西:
doc ^? key "body" . values
. indexWith (key "key")
. key "data" . values
. key "foo" . withIndex
应返回
[(23, 1), (23, 2)]
MVCE:
#!/usr/bin/env stack
-- stack --resolver lts-11.7 script
-- --package lens
-- --package text
-- --package lens-aeson
{-# LANGUAGE OverloadedStrings #-}
import Control.Lens
import Data.Aeson.Lens
import Data.Text
doc :: Text
doc = "{\"body\": [{\"key\": 23, \"data\": [{\"foo\": 1}, {\"foo\": 2}]}]}"
-- Something akin to Lens -> Traversal -> IndexedTraversal
indexWith :: _
indexWith = undefined
-- should produce [(23, 1), (23, 2)]
indexedBody :: [(Int, Int)]
indexedBody = doc ^? key "body" . values
. indexWith (key "key")
. key "data" . values
. key "foo" . withIndex
main = print indexedBody
答案 0 :(得分:3)
我终于用GHC回到了真正的计算机上,并做了一些更彻底的测试。我发现了两件事:1)我的基本想法有效。 2)按照你想要的方式使用它有一个微妙的很多。
以下是开始实验的一些扩展定义:
{-# Language OverloadedStrings, FlexibleContexts #-}
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.Text
import Data.Monoid (First)
import Data.Maybe (isJust, fromJust)
doc :: Text
doc = "{\"body\": [ {\"key\": 23, \"data\": [{\"foo\": 1}, {\"foo\": 2}]}, {\"key\": 29, \"data\": [{\"foo\": 11}, {\"bar\": 12}]} ]}"
doc2 :: Text
doc2 = "{\"body\": [ {\"data\": [{\"foo\": 21}, {\"foo\": 22}]}, {\"key\": 23, \"data\": [{\"foo\": 1}, {\"foo\": 2}]}, {\"key\": 29, \"data\": [{\"foo\": 11}, {\"bar\": 12}]} ]}"
subIndex :: Indexable i p => Getting i s i -> p s fb -> s -> fb
subIndex f = reindexed (view f) selfIndex
subIndex2 :: Indexable (Maybe a) p => Getting (First a) s a -> p s fb -> s -> fb
subIndex2 f = reindexed (preview f) selfIndex
subIndex3 :: (Applicative f, Indexable i p) => Getting (First i) s i -> p s (f s) -> s -> f s
subIndex3 f = reindexed fromJust (subIndex2 f . indices isJust)
我已经定义了3种不同的函数变体来做你想做的事情。第一个subIndex
,正是您在问题标题中提出的要求。它需要一个镜头,而不是遍历。这可以防止它完全按照您想要的方式使用。
> doc ^@.. key "body" . values . subIndex (key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
<interactive>:61:42: error:
• No instance for (Monoid Integer) arising from a use of ‘key’
• In the first argument of ‘(.)’, namely ‘key "key"’
In the first argument of ‘subIndex’, namely
‘(key "key" . _Integer)’
In the first argument of ‘(<.)’, namely
‘subIndex (key "key" . _Integer)’
这里的问题是密钥可能实际上并不存在。类型系统携带足够的信息来检测这个问题,并拒绝编译。您可以通过一些小修改来解决它:
> doc ^@.. key "body" . values . subIndex (singular $ key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(23,1),(23,2),(29,11)]
但是singular
是对编译器的承诺。如果你错了,那就出错了:
> doc2 ^@.. key "body" . values . subIndex (singular $ key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(*** Exception: singular: empty traversal
CallStack (from HasCallStack):
error, called at src/Control/Lens/Traversal.hs:667:46 in lens-4.16-f58XaBDme4ClErcSwBN5e:Control.Lens.Traversal
singular, called at <interactive>:63:43 in interactive:Ghci4
所以,我的下一个想法是使用preview
而不是view
,这导致subIndex2
。
> doc ^@.. key "body" . values . subIndex2 (key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(Just 23,1),(Just 23,2),(Just 29,11)]
在那里安装那些Just
构造函数有点难看,但它有其优点:
> doc2 ^@.. key "body" . values . subIndex2 (key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(Nothing,21),(Nothing,22),(Just 23,1),(Just 23,2),(Just 29,11)]
有了这个,即使索引丢失,遍历仍然会击中所有常规目标。这可能是解决方案领域的一个有趣点。肯定有用例,它将是最佳选择。尽管如此,我认为它并不完全是你想要的。我想你可能真的想要Traversal-ish行为 - 如果没有索引遍历的目标,只需跳过所有孩子。不幸的是,镜头对指数的这种操纵有点严峻。我最终得到了subIndex3
,它使用了map fromJust . filter isJust
模式的索引级变体。它本身非常安全,但在重构方面它有点脆弱。
但它有效:
> doc ^@.. key "body" . values . subIndex3 (key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(23,1),(23,2),(29,11)]
并且,当索引遍历找不到任何目标时,它可能会像您一样预期:
> doc2 ^@.. key "body" . values . subIndex3 (key "key" . _Integer) <. key "data" . values . key "foo" . _Integer
[(23,1),(23,2),(29,11)]
缺少"key"
字段的字典只会被忽略,即使遍历的其余部分也包含目标。
所以你去了 - 三个相关的选项,每个选项都有正面和负面。第三个在实施方面非常粗糙,我怀疑它也不会有最好的表现。但我估计它最有可能是你真正想要的。
这不是一个完整的答案,因为我没有配备ghc的计算机 - 我已经通过与freenode上的lambdabot聊天来测试。
09:34 <me> > let setIndex f = reindexed (view f) selfIndex in Just (1, [3..6]) ^@.. _Just . setIndex _1 <. _2 . traverse
09:34 <lambdabot> [(1,3),(1,4),(1,5),(1,6)]
我认为这是您正在寻找的基本想法,但我还没有将其应用于您的数据。我将它应用到一个结构上类似于证明模式的值,至少。基本思想是使用selfIndex
和reindexed
的组合来创建具有正确索引值的索引光学元件。然后你必须小心(<.)
和类似的运算符,以保持各种索引光学组合的正确索引。
最后,我转而使用(^@..)
来提取(索引,目标)对的列表,而不是使用withIndex
。后者会起作用,但是你需要更加小心地将各种成分联系在一起。
使用withIndex
的示例,请注意,为了工作,它需要覆盖合成运算符的默认关联:
12:21 <me> > let setIndex f = reindexed (view f) selfIndex in Just (1, [3..6]) ^.. (_Just . setIndex _1 <. _2 . traverse) . withIndex
12:21 <lambdabot> [(1,3),(1,4),(1,5),(1,6)]
答案 1 :(得分:2)
仅仅Fold
- 不是一个完整的Traversal
- 是否足够?
Control.Lens.Reified
为ReifiedFold
新类型提供了有用的实例。特别是,Applicative
实例执行折叠的笛卡尔乘积。
我们可以使用该笛卡尔积来获得&#34;键&#34;一方面,&#34;数据&#34;在另一。像这样:
indexedBody :: Fold Value (Int,Int)
indexedBody =
let k :: Fold Value Int
k = key "key"._Integral
d :: Fold Value Int
d = key "data".values.key "foo"._Integral
Fold kd = (,) <$> Fold k <*> Fold d
in key "body" . values . kd
没有组合爆炸,因为&#34;键&#34;部分目标最多只有一个值。