#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 _ _ _