Индекс Обход с помощью объектива - PullRequest
0 голосов
/ 03 мая 2018

У меня есть объектив , указывающий на документ json, например,

doc ^? ((key "body").values)

Теперь я бы хотел проиндексировать значения в теле с помощью клавиши «ключ», потому что 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

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Достаточно ли просто Fold - не полный Traversal?

Control.Lens.Reified предоставляет новый тип ReifiedFold с полезными экземплярами. В частности, экземпляр Applicative выполняет декартово произведение складок.

Мы можем использовать это декартово произведение, чтобы получить «ключ» с одной стороны и «данные» с другой. Как это:

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

Там нет комбинаторного взрыва, потому что "ключевая" часть нацелена не более чем на одно значение.

0 голосов
/ 03 мая 2018

Новый, отвратительно полный ответ

Я наконец-то вернулся на реальный компьютер с 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)]

При этом обход по-прежнему поражает все свои обычные цели, даже если индекс отсутствует. Это потенциально интересная точка в пространстве решений. Есть, конечно, варианты использования, для которых это будет лучшим выбором. Несмотря на это, я подумал, что это не совсем то, что вы хотели. Я подумал, что вы, вероятно, действительно хотели поведение обхода - если нет цели для обхода индекса, просто пропустите все дочерние элементы. К сожалению, линза немного аскетична в манипуляциях с индексами. В итоге я получил 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 - я тестировал, общаясь с lambdabot на freenode.

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 для создания индексированной оптики с правильным значением индекса. Тогда вы должны быть осторожны с (<.) и подобными операторами для поддержания правильного индекса по составам различных индексированных оптик.

Наконец, я переключился на использование (^@..) для извлечения списка пар (index, target) вместо использования 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)]
...