Как избежать возвращаемого значения по умолчанию при доступе к несуществующему полю с объективами?
Мне нравится библиотека Lens, и мне нравится, как она работает, но иногда она вводит столько проблем, что я сожалею, что когда-либо начал ее использовать. Давайте рассмотрим этот простой пример:
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Data = A { _x :: String, _y :: String }
| B { _x :: String }
makeLenses ''Data
main = do
let b = B "x"
print $ view y b
выводится:
""
А теперь представьте себе - у нас есть тип данных, и мы реорганизуем его - путем изменения некоторых имен. Вместо того, чтобы получать ошибку (во время выполнения, например, с обычными аксессуарами), что это имя больше не применяется к конкретному конструктору данных, объективы используют mempty
из Monoid
для создания объекта по умолчанию, поэтому мы получаем странные результаты вместо ошибки. Отладка чего-то подобного почти невозможна.
Есть ли способ исправить это поведение? Я знаю, что есть некоторые специальные операторы, чтобы получить поведение, которое я хочу, но все "нормальные" функции от объективов просто ужасны. Должен ли я просто переопределять их с помощью моего настраиваемого модуля или есть ли более удобный метод?
В качестве побочного элемента: я хочу иметь возможность читать и устанавливать аргументы с использованием синтаксиса объектива, но просто удалять поведение автоматического создания результата, когда поле отсутствует.
Ответы
Ответ 1
Похоже, вы просто хотите восстановить поведение исключения. Я смутно помню, что это то, как однажды разобрался вид. Если это так, я ожидаю, что с изменением будет сделан разумный выбор.
Обычно я заканчиваю работу с (^?) в тех случаях, о которых вы говорите:
> b ^? y
Nothing
Если вы хотите использовать поведение исключения, вы можете использовать ^?!
> b ^?! y
"*** Exception: (^?!): empty Fold
Я предпочитаю использовать ^?
, чтобы избежать частичных функций и исключений, подобно тому, как обычно рекомендуется избегать head
, last
, !!
и других частичных функций.
Ответ 2
Да, я тоже немного странно, что view
работает для Traversal
, объединяя цели. Я думаю, что это из-за экземпляра Monoid m => Applicative (Const m)
. Вы можете написать собственный эквивалент view
, который не имеет такого поведения, написав собственный эквивалент Const
, который не имеет этого экземпляра.
Возможно, одним из способов было бы обеспечить подпись типа для y
, поэтому знаете точно, что это такое. Если у вас это было, ваше "патологическое" использование view
не будет компилироваться.
data Data = A { _x :: String, _y' :: String }
| B { _x :: String }
makeLenses ''Data
y :: Lens' Data String
y = y'
Ответ 3
Вы можете сделать это, указав свой собственный оператор view1
. Он не существует в пакете lens
, но его легко определить локально.
{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
data Data = A { _x :: String, _y :: String }
| B { _x :: String }
makeLenses ''Data
newtype Get a b = Get { unGet :: a }
instance Functor (Get a) where
fmap _ (Get x) = Get x
view1 :: LensLike' (Get a) s a -> s -> a
view1 l = unGet . l Get
works :: Data -> String
works = view1 x
-- fails :: Data -> String
-- fails = view1 y
-- Bug.hs:23:15:
-- No instance for (Control.Applicative.Applicative (Get String))
-- arising from a use of ‘y’