2014-12-19 2 views
10

Мне нравится библиотека 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 для создания объекта по умолчанию, поэтому мы получаем странные результаты вместо ошибки. Отладка чего-то подобного почти невозможна. Есть ли способ исправить это поведение? Я знаю, что есть некоторые специальные операторы, чтобы получить поведение, которое я хочу, но все «нормальные» функции от объективов просто ужасны. Должен ли я просто переопределять их с помощью моего настраиваемого модуля или есть ли более хороший метод?

В качестве побочного элемента: я хочу, чтобы читать и устанавливать аргументы с использованием синтаксиса объектива, но просто удалять поведение автоматического создания результата, когда поле отсутствует.

+7

'Объективы предназначены главным образом для чистых видов продукции. Для типов сумм есть «Призма». Возможно, 'makeLenses' должен полностью отклонить ADT с несколькими конструкторами, но я полагаю, что иногда это полезно, что позволяет им. И довольно легко вспомнить, что вам нужно следить за использованием объективов с несколькими конструкторами, не так ли? – leftaroundabout

+0

@leftaroundabout Хорошо, но могу ли я читать и писать значения при использовании 'makePrisms'? Я не читаю каждое значение как 'Maybe' (используя' preview').Я просто хочу читать и устанавливать значения, как позволяют обычные типы данных Haskell. Как это возможно? –

+4

@Wojciech Типы записей с более чем одним конструктором, как правило, плохая идея, ИМХО. – danidiaz

ответ

1

Да, я тоже немного странно, что view работает для Traversal s путем конкатенации целей. Я думаю, что это из-за экземпляра 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' 
5

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

Обычно я в конечном итоге работает с в тех случаях, о котором вы говорите (^?):

> b ^? y 
Nothing 

Если вы хотите, чтобы поведение исключения вы можете использовать ^?!

> b ^?! y 
"*** Exception: (^?!): empty Fold 

Я предпочитаю использовать ^? во избежание частичных функций и исключений, аналогично тому, как обычно рекомендуется держаться подальше от head, last, !! и других частичных функций.

1

Вы можете сделать это, указав свой собственный оператор 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’