Использование линз для сопоставления двух полей записи

#haskell #haskell-lens

Вопрос:

Я пытаюсь привыкнуть к некоторым базовым lens функциям. Я начал со следующего типа и функции, прежде чем пытаться ввести линзы:

 import qualified Data.Set as S

data Sets = Sets {pending, placed, open :: S.Set Int}
interesting :: Sets -> [Int]
interesting = toList . pending <> placed
 

т. Е. я хочу объединение ожидающих и размещенных узлов, выраженное в виде списка (позже я использую результат для понимания списка, поэтому набор неудобен).

Мой основной вопрос: как мне воспроизвести это с помощью инструментов из lens ? То, что следует ниже, можно пропустить, если у вас есть хороший ответ на этот вопрос; это запись моих собственных исследований этого пространства для начинающих.

Я переименовал поля, чтобы присвоить себе линзы:

 {-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import qualified Data.Set as S

data Sets = Sets {_pending, _placed, _open :: S.Set Int}
makeLenses ''Sets
 

и теперь хотите переопределить interesting . Конечно, это несложно сделать без lenses ( toList . _pending <> _placed ) , но я пытаюсь освоиться с линзами, и это кажется полезным упражнением.

Моя первая мысль заключается в том, что pending and placed по-прежнему являются обеими функциями, и я все еще хочу точечно отображать вещи, которые как-то связаны с их результатами, но не совсем, поэтому pending <> placed должно быть, по крайней мере, интересно посмотреть:

 *Main Data.Foldable> :t pending <> placed
pending <> placed
  :: (Semigroup (f Sets), Functor f) =>
     (S.Set Int -> f (S.Set Int)) -> Sets -> f Sets
 

Итак, что это за тип и какие операции я могу выполнить с ним? Возможно, это выглядит как ограничение Getter , хотя я не могу заставить GHCI сообщить мне, каковы ограничения, путем записи :t pending <> placed :: Getter _s _a . Мы можем попробовать передать его в view любом случае, который хочет Getter , и это работает:

 *Main Data.Foldable> :t view (pending <> placed)
view (pending <> placed) :: MonadReader Sets m => m (S.Set Int)
 

что, хорошо, это обобщение Sets -> S.Set Int , и я могу составить его, toList чтобы вернуть то, с чего я должен был начать:

 *Main Data.Foldable> :t toList . view (pending <> placed)
toList . view (pending <> placed) :: Sets -> [Int]
 

Но это не кажется очень удовлетворительным: это то, что у меня было раньше, но с дополнительным view вызовом, и я не чувствую, что использовал здесь какую-либо силу линз. Я также не совсем понимаю, что pending <> placed «означает» в этом контексте.

Другая вещь, которую я рассмотрел, это то, что то, что я хочу сделать, очень похоже foldMap , и то, что у меня есть, похоже на a Getter , так что я должен быть в состоянии сделать что-то foldMapOf .

 *Main Data.Foldable> :t foldMapOf (pending <> placed)
foldMapOf (pending <> placed)
  :: Semigroup r => (S.Set Int -> r) -> Sets -> r
 

Для этого нужен еще один аргумент, и очевидным кандидатом является toList :

 *Main Data.Foldable> :t foldMapOf (pending <> placed) toList
foldMapOf (pending <> placed) toList :: Sets -> [Int]
 

У этого есть правильный тип, но, увы, другая семантика: он использует <> после преобразования в [Int] , а не на базовом Set Int s , поэтому, если _pending и _placed совместно использовать элементы, мы получаем дубликаты копий в результате.

Еще одна вещь, которую я мог бы сделать, это использовать toListOf (pending <> placed) , получая список наборов, а затем использовать обычные функции без линз, чтобы объединить их вместе:

 *Main Data.Foldable> :t toList . mconcat . toListOf (pending <> placed)
toList . mconcat . toListOf (pending <> placed) :: Sets -> [Int]
 

Это работает, но довольно уродливо и, похоже, упускает суть.

Итак, дают ли линзы мне здесь какие-нибудь лучшие инструменты? Я выбрал проблему настолько простую, что не вижу преимущества линз перед простыми средствами получения полей записей?

Комментарии:

1. Я не эксперт по линзам, но я действительно не думаю, что линзам есть что предложить вам здесь. Каждая линза позволяет вам манипулировать определенной частью Sets , но все, что вы хотите сделать, чтобы объединить эти манипуляции, будет зависеть только от того, что они собой представляют. В частности, поскольку линзы Van Laarhoven (по крайней мере) не имеют какой-либо ортогональности, нет канонического способа объединить изменения через две линзы в одну и ту же структуру.

2. Спасибо, @dfeuer. Я, по крайней мере, нашел хороший инструмент lens для другой операции с этим типом: удаление Int из одного из наборов и добавление его в другой. То есть moveNode idx from to = over from (S.delete idx) . over to (S.insert idx) .

3. Конечно, но это просто использование одного объектива, а затем другого. Объединение не происходит.

Ответ №1:

Я выбрал проблему настолько простую, что не вижу преимущества линз перед простыми средствами получения полей записей?

Я бы сказал, что это в значительной степени так. Интуитивно понятно, pending <> placed что это цель только для чтения: нет разумного способа изменить объединение двух наборов как часть Sets структуры, поскольку оно фактически не соответствует ничему в нем. Вот почему в итоге вы получаете средство получения, которое, как вы выяснили, по сути, является функцией.

 *Main Data.Foldable> :t pending <> placed
pending <> placed
  :: (Semigroup (f Sets), Functor f) =>
     (S.Set Int -> f (S.Set Int)) -> Sets -> f Sets
 

Итак, что это за тип и какие операции я могу выполнить с ним? Возможно, это
похоже на ограниченный способ получения, хотя я не могу получить
GHCI, чтобы сообщить мне, каковы ограничения, написав:t в ожидании <>
размещено :: Getter _s _a .

Хотя тип допускает некоторые другие не очень важные вещи, то, что вы действительно хотите от него f ~ Const (S.Set Int) , это то, что делает отображение на линзах фактически отображающим извлеченные наборы. Специализация на Const дает вам геттер или, будучи суетливым, Getting . :t это немного более полезно:

 ghci> :t pending <> placed :: Getting _ _ _

<interactive>:1:34: error:
    • Found type wildcard ‘_’ standing for ‘S.Set Int’
      To use the inferred type, enable PartialTypeSignatures
    • In the third argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _

<interactive>:1:32: error:
    • Found type wildcard ‘_’ standing for ‘Sets’
      To use the inferred type, enable PartialTypeSignatures
    • In the second argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _

<interactive>:1:30: error:
    • Found type wildcard ‘_’ standing for ‘_’
      Where: ‘_’ is a rigid type variable bound by
               the inferred type of
                 it :: Semigroup _ => Getting _ Sets (S.Set Int)
               at <interactive>:1:1
      To use the inferred type, enable PartialTypeSignatures
    • In the first argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _