Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid default return value when accessing a non-existent field with lenses?

I love Lens library and I love how it works, but sometimes it introduces so many problems, that I regret I ever started using it. Lets look at this simple example:

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

it outputs:

""

And now imagine - we've got a datatype and we refactor it - by changing some names. Instead of getting error (in runtime, like with normal accessors) that this name does not longer apply to particular data constructor, lenses use mempty from Monoid to create default object, so we get strange results instead of error. Debugging something like this is almost impossible. Is there any way to fix this behaviour? I know there are some special operators to get the behaviour I want, but all "normal" looking functions from lenses are just horrible. Should I just override them with my custom module or is there any nicer method?

As a sidenote: I want to be able to read and set the arguments using lens syntax, but just remove the behaviour of automatic result creating when field is missing.

like image 889
Wojciech Danilo Avatar asked Dec 19 '14 14:12

Wojciech Danilo


2 Answers

It sounds like you just want to recover the exception behavior. I vaguely recall that this is how view once worked. If so, I expect a reasonable choice was made with the change.

Normally I end up working with (^?) in the cases you are talking about:

> b ^? y
Nothing

If you want the exception behavior you can use ^?!

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

I prefer to use ^? to avoid partial functions and exceptions, similar to how it is commonly advised to stay away from head, last, !! and other partial functions.

like image 50
Davorak Avatar answered Sep 19 '22 10:09

Davorak


Yes, I too have found it a bit odd that view works for Traversals by concatenating the targets. I think this is because of the instance Monoid m => Applicative (Const m). You can write your own view equivalent that doesn't have this behaviour by writing your own Const equivalent that doesn't have this instance.

Perhaps one workaround would be to provide a type signature for y, so know know exactly what it is. If you had this then your "pathological" use of view wouldn't compile.

data Data = A { _x :: String, _y' :: String }
          | B { _x :: String }

makeLenses ''Data

y :: Lens' Data String
y = y'
like image 31
Tom Ellis Avatar answered Sep 19 '22 10:09

Tom Ellis