Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Control/Lens/Iso.hs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module Control.Lens.Iso
, simple
, non, non'
, anon
, inon
, enum
, curried, uncurried
, flipped
Expand Down Expand Up @@ -397,6 +398,32 @@ anon a p = iso (fromMaybe a) go where
| otherwise = Just b
{-# INLINE anon #-}

-- | Improper 'non' - this is a version of 'non' suitable for caching.
-- Use 'inon' instead of 'non' to manage a map of values where you don't
-- want to lose information about which keys were ever present in the map.
--
-- The expression @'non' a@ maps @Nothing@ to @a@ and @a@ back to @Nothing@,
-- so it is a proper 'Iso'. Instead, @'inon' a@ still maps @Nothing@
-- to @a@, but in the reverse direction it maps @a@ back to @Just a@.
-- If you have a map of values such as @Map UserId UserProfile@ which
-- you are accessing using 'non' with a lens like @at uid . non def
-- . somefield@, when you set that field to a value that happens to
-- restore the condition @UserProfile == def@, the record gets deleted
-- from the map. You have now lost the information that that user was
-- ever present in the map:
--
-- >>> fromList [("user1", "axc")] & (at "user1" . non "abc" . ix 1) .~ 'b'
-- fromlist []
--
-- Using 'inon' instead of 'non' means that key will not be deleted
-- from the map:
--
-- >>> fromList [("user1", "axc")] & (at "user1" . inon "abc" . ix 1) .~ 'b'
-- fromlist [("user1", "abc")]
inon :: a -> Iso' (Maybe a) a
inon a = anon a (const False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a gut feeling that you be ok with inon :: a -> Setter (Maybe a) a, which isn't as unlawful. (It still changes Nothing to Just a when you %~ id).

I'd ask you to make it a setter and move to Unsound module.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then I would have to pass both a getter and a setter to the function, which seems the opposite direction from where I am trying to go. I will give it some thought.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll think more about this too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultimately I just started writing anon v (const False) to avoid this problem.

{-# INLINE inon #-}

-- | The canonical isomorphism for currying and uncurrying a function.
--
-- @
Expand Down