optics-core-0.2: src/Optics/At/Core.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeInType #-}
-- |
-- Module: Optics.At.Core
-- Description: Optics for 'Map' and 'Set'-like containers.
--
-- This module provides optics for 'Map' and 'Set'-like containers, including an
-- 'AffineTraversal' to traverse a key in a map or an element of a sequence:
--
-- >>> preview (ix 1) ['a','b','c']
-- Just 'b'
--
-- a 'Lens' to get, set or delete a key in a map:
--
-- >>> set (at 0) (Just 'b') (Map.fromList [(0, 'a')])
-- fromList [(0,'b')]
--
-- and a 'Lens' to insert or remove an element of a set:
--
-- >>> IntSet.fromList [1,2,3,4] & contains 3 .~ False
-- fromList [1,2,4]
--
-- The @Optics.At@ module from @optics-extra@ provides additional instances of
-- the classes defined here.
--
module Optics.At.Core
(
-- * Type families
Index
, IxValue
-- * Ixed
, Ixed(..)
, ixAt
-- * At
, At(..)
, at'
, sans
-- * Contains
, Contains(..)
) where
import Data.Array.IArray as Array
import Data.Array.Unboxed
import Data.Complex
import Data.Functor.Identity
import Data.IntMap as IntMap
import Data.IntSet as IntSet
import Data.Kind (Type)
import Data.List.NonEmpty as NonEmpty
import Data.Map as Map
import Data.Sequence as Seq
import Data.Set as Set
import Data.Tree
import Data.Maybe.Optics
import Optics.AffineTraversal
import Optics.Iso
import Optics.Lens
import Optics.Optic
import Optics.Setter
-- | Type family that takes a key-value container type and returns the type of
-- keys (indices) into the container, for example @'Index' ('Map' k a) ~ k@.
-- This is shared by 'Ixed', 'At' and 'Contains'.
type family Index (s :: Type) :: Type
type instance Index (e -> a) = e
type instance Index IntSet = Int
type instance Index (Set a) = a
type instance Index [a] = Int
type instance Index (NonEmpty a) = Int
type instance Index (Seq a) = Int
type instance Index (a,b) = Int
type instance Index (a,b,c) = Int
type instance Index (a,b,c,d) = Int
type instance Index (a,b,c,d,e) = Int
type instance Index (a,b,c,d,e,f) = Int
type instance Index (a,b,c,d,e,f,g) = Int
type instance Index (a,b,c,d,e,f,g,h) = Int
type instance Index (a,b,c,d,e,f,g,h,i) = Int
type instance Index (IntMap a) = Int
type instance Index (Map k a) = k
type instance Index (Array.Array i e) = i
type instance Index (UArray i e) = i
type instance Index (Complex a) = Int
type instance Index (Identity a) = ()
type instance Index (Maybe a) = ()
type instance Index (Tree a) = [Int]
-- | This class provides a simple 'Lens' that lets you view (and modify)
-- information about whether or not a container contains a given 'Index'.
-- Instances are provided for 'Set'-like containers only.
class Contains m where
-- |
-- >>> IntSet.fromList [1,2,3,4] ^. contains 3
-- True
--
-- >>> IntSet.fromList [1,2,3,4] ^. contains 5
-- False
--
-- >>> IntSet.fromList [1,2,3,4] & contains 3 .~ False
-- fromList [1,2,4]
contains :: Index m -> Lens' m Bool
instance Contains IntSet where
contains k = lensVL $ \f s -> f (IntSet.member k s) <&> \b ->
if b then IntSet.insert k s else IntSet.delete k s
{-# INLINE contains #-}
instance Ord a => Contains (Set a) where
contains k = lensVL $ \f s -> f (Set.member k s) <&> \b ->
if b then Set.insert k s else Set.delete k s
{-# INLINE contains #-}
-- | Type family that takes a key-value container type and returns the type of
-- values stored in the container, for example @'IxValue' ('Map' k a) ~ a@. This
-- is shared by both 'Ixed' and 'At'.
type family IxValue (m :: Type) :: Type
-- | Provides a simple 'AffineTraversal' lets you traverse the value at a given
-- key in a 'Map' or element at an ordinal position in a list or 'Seq'.
class Ixed m where
-- | Type family that takes a key-value container type and returns the kind
-- of optic to index into it. For most containers, it's 'An_AffineTraversal',
-- @Representable@ (Naperian) containers it is 'A_Lens', and multi-maps would
-- have 'A_Traversal'.
type IxKind (m :: Type) :: OpticKind
type IxKind m = An_AffineTraversal
-- | /NB:/ Setting the value of this 'AffineTraversal' will only set the value
-- in 'at' if it is already present.
--
-- If you want to be able to insert /missing/ values, you want 'at'.
--
-- >>> [1,2,3,4] & ix 2 %~ (*10)
-- [1,2,30,4]
--
-- >>> "abcd" & ix 2 .~ 'e'
-- "abed"
--
-- >>> "abcd" ^? ix 2
-- Just 'c'
--
-- >>> [] ^? ix 2
-- Nothing
ix :: Index m -> Optic' (IxKind m) NoIx m (IxValue m)
default ix :: (At m, IxKind m ~ An_AffineTraversal) => Index m -> Optic' (IxKind m) NoIx m (IxValue m)
ix = ixAt
{-# INLINE ix #-}
-- | A definition of 'ix' for types with an 'At' instance. This is the default
-- if you don't specify a definition for 'ix'.
ixAt :: At m => Index m -> AffineTraversal' m (IxValue m)
ixAt = \i -> at i % _Just
{-# INLINE ixAt #-}
type instance IxValue (e -> a) = a
instance Eq e => Ixed (e -> a) where
type IxKind (e -> a) = A_Lens
ix e = lensVL $ \p f -> p (f e) <&> \a e' -> if e == e' then a else f e'
{-# INLINE ix #-}
type instance IxValue (Maybe a) = a
instance Ixed (Maybe a) where
ix () = castOptic @An_AffineTraversal _Just
{-# INLINE ix #-}
type instance IxValue [a] = a
instance Ixed [a] where
ix k = atraversalVL (ixListVL k)
{-# INLINE ix #-}
type instance IxValue (NonEmpty a) = a
instance Ixed (NonEmpty a) where
ix k = atraversalVL $ \point f xs0 ->
if k < 0
then point xs0
else let go (a:|as) 0 = f a <&> (:|as)
go (a:|as) i = (a:|) <$> ixListVL (i - 1) point f as
in go xs0 k
{-# INLINE ix #-}
type instance IxValue (Identity a) = a
instance Ixed (Identity a) where
type IxKind (Identity a) = An_Iso
ix () = coerced
{-# INLINE ix #-}
type instance IxValue (Tree a) = a
instance Ixed (Tree a) where
ix xs0 = atraversalVL $ \point f ->
let go [] (Node a as) = f a <&> \a' -> Node a' as
go (i:is) t@(Node a as)
| i < 0 = point t
| otherwise = Node a <$> ixListVL i point (go is) as
in go xs0
{-# INLINE ix #-}
type instance IxValue (Seq a) = a
instance Ixed (Seq a) where
ix i = atraversalVL $ \point f m ->
if 0 <= i && i < Seq.length m
then f (Seq.index m i) <&> \a -> Seq.update i a m
else point m
{-# INLINE ix #-}
type instance IxValue (IntMap a) = a
-- Default implementation uses IntMap.alterF
instance Ixed (IntMap a)
type instance IxValue (Map k a) = a
-- Default implementation uses Map.alterF
instance Ord k => Ixed (Map k a)
type instance IxValue (Set k) = ()
instance Ord k => Ixed (Set k) where
ix k = atraversalVL $ \point f m ->
if Set.member k m
then f () <&> \() -> Set.insert k m
else point m
{-# INLINE ix #-}
type instance IxValue IntSet = ()
instance Ixed IntSet where
ix k = atraversalVL $ \point f m ->
if IntSet.member k m
then f () <&> \() -> IntSet.insert k m
else point m
{-# INLINE ix #-}
type instance IxValue (Array.Array i e) = e
-- |
-- @
-- arr 'Array.!' i ≡ arr 'Optics.Operators.^.' 'ix' i
-- arr '//' [(i,e)] ≡ 'ix' i 'Optics.Operators..~' e '$' arr
-- @
instance Ix i => Ixed (Array.Array i e) where
ix i = atraversalVL $ \point f arr ->
if inRange (bounds arr) i
then f (arr Array.! i) <&> \e -> arr Array.// [(i,e)]
else point arr
{-# INLINE ix #-}
type instance IxValue (UArray i e) = e
-- |
-- @
-- arr 'Array.!' i ≡ arr 'Optics.Operators.^.' 'ix' i
-- arr '//' [(i,e)] ≡ 'ix' i 'Optics.Operators..~' e '$' arr
-- @
instance (IArray UArray e, Ix i) => Ixed (UArray i e) where
ix i = atraversalVL $ \point f arr ->
if inRange (bounds arr) i
then f (arr Array.! i) <&> \e -> arr Array.// [(i,e)]
else point arr
{-# INLINE ix #-}
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a) a@
type instance IxValue (a0, a2) = a0
instance (a0 ~ a1) => Ixed (a0, a1) where
ix i = atraversalVL $ \point f ~s@(a0, a1) ->
case i of
0 -> (,a1) <$> f a0
1 -> (a0,) <$> f a1
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a) a@
type instance IxValue (a0, a1, a2) = a0
instance (a0 ~ a1, a0 ~ a2) => Ixed (a0, a1, a2) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2) ->
case i of
0 -> (,a1,a2) <$> f a0
1 -> (a0,,a2) <$> f a1
2 -> (a0,a1,) <$> f a2
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3) = a0
instance (a0 ~ a1, a0 ~ a2, a0 ~ a3) => Ixed (a0, a1, a2, a3) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3) ->
case i of
0 -> (,a1,a2,a3) <$> f a0
1 -> (a0,,a2,a3) <$> f a1
2 -> (a0,a1,,a3) <$> f a2
3 -> (a0,a1,a2,) <$> f a3
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3, a4) = a0
instance (a0 ~ a1, a0 ~ a2, a0 ~ a3, a0 ~ a4) => Ixed (a0, a1, a2, a3, a4) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3, a4) ->
case i of
0 -> (,a1,a2,a3,a4) <$> f a0
1 -> (a0,,a2,a3,a4) <$> f a1
2 -> (a0,a1,,a3,a4) <$> f a2
3 -> (a0,a1,a2,,a4) <$> f a3
4 -> (a0,a1,a2,a3,) <$> f a4
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3, a4, a5) = a0
instance
(a0 ~ a1, a0 ~ a2, a0 ~ a3, a0 ~ a4, a0 ~ a5
) => Ixed (a0, a1, a2, a3, a4, a5) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3, a4, a5) ->
case i of
0 -> (,a1,a2,a3,a4,a5) <$> f a0
1 -> (a0,,a2,a3,a4,a5) <$> f a1
2 -> (a0,a1,,a3,a4,a5) <$> f a2
3 -> (a0,a1,a2,,a4,a5) <$> f a3
4 -> (a0,a1,a2,a3,,a5) <$> f a4
5 -> (a0,a1,a2,a3,a4,) <$> f a5
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3, a4, a5, a6) = a0
instance
(a0 ~ a1, a0 ~ a2, a0 ~ a3, a0 ~ a4, a0 ~ a5, a0 ~ a6
) => Ixed (a0, a1, a2, a3, a4, a5, a6) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3, a4, a5, a6) ->
case i of
0 -> (,a1,a2,a3,a4,a5,a6) <$> f a0
1 -> (a0,,a2,a3,a4,a5,a6) <$> f a1
2 -> (a0,a1,,a3,a4,a5,a6) <$> f a2
3 -> (a0,a1,a2,,a4,a5,a6) <$> f a3
4 -> (a0,a1,a2,a3,,a5,a6) <$> f a4
5 -> (a0,a1,a2,a3,a4,,a6) <$> f a5
6 -> (a0,a1,a2,a3,a4,a5,) <$> f a6
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a, a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3, a4, a5, a6, a7) = a0
instance
(a0 ~ a1, a0 ~ a2, a0 ~ a3, a0 ~ a4, a0 ~ a5, a0 ~ a6, a0 ~ a7
) => Ixed (a0, a1, a2, a3, a4, a5, a6, a7) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3, a4, a5, a6, a7) ->
case i of
0 -> (,a1,a2,a3,a4,a5,a6,a7) <$> f a0
1 -> (a0,,a2,a3,a4,a5,a6,a7) <$> f a1
2 -> (a0,a1,,a3,a4,a5,a6,a7) <$> f a2
3 -> (a0,a1,a2,,a4,a5,a6,a7) <$> f a3
4 -> (a0,a1,a2,a3,,a5,a6,a7) <$> f a4
5 -> (a0,a1,a2,a3,a4,,a6,a7) <$> f a5
6 -> (a0,a1,a2,a3,a4,a5,,a7) <$> f a6
7 -> (a0,a1,a2,a3,a4,a5,a6,) <$> f a7
_ -> point s
-- | @'ix' :: 'Int' -> 'AffineTraversal'' (a, a, a, a, a, a, a, a, a) a@
type instance IxValue (a0, a1, a2, a3, a4, a5, a6, a7, a8) = a0
instance
(a0 ~ a1, a0 ~ a2, a0 ~ a3, a0 ~ a4, a0 ~ a5, a0 ~ a6, a0 ~ a7, a0 ~ a8
) => Ixed (a0, a1, a2, a3, a4, a5, a6, a7, a8) where
ix i = atraversalVL $ \point f ~s@(a0, a1, a2, a3, a4, a5, a6, a7, a8) ->
case i of
0 -> (,a1,a2,a3,a4,a5,a6,a7,a8) <$> f a0
1 -> (a0,,a2,a3,a4,a5,a6,a7,a8) <$> f a1
2 -> (a0,a1,,a3,a4,a5,a6,a7,a8) <$> f a2
3 -> (a0,a1,a2,,a4,a5,a6,a7,a8) <$> f a3
4 -> (a0,a1,a2,a3,,a5,a6,a7,a8) <$> f a4
5 -> (a0,a1,a2,a3,a4,,a6,a7,a8) <$> f a5
6 -> (a0,a1,a2,a3,a4,a5,,a7,a8) <$> f a6
7 -> (a0,a1,a2,a3,a4,a5,a6,,a8) <$> f a7
8 -> (a0,a1,a2,a3,a4,a5,a6,a7,) <$> f a8
_ -> point s
-- | 'At' provides a 'Lens' that can be used to read, write or delete the value
-- associated with a key in a 'Map'-like container on an ad hoc basis.
--
-- An instance of 'At' should satisfy:
--
-- @
-- 'ix' k ≡ 'at' k '%' '_Just'
-- @
class (Ixed m, IxKind m ~ An_AffineTraversal) => At m where
-- |
-- >>> Map.fromList [(1,"world")] ^. at 1
-- Just "world"
--
-- >>> at 1 ?~ "hello" $ Map.empty
-- fromList [(1,"hello")]
--
-- /Note:/ Usage of this function might introduce space leaks if you're not
-- careful to make sure that values put inside the 'Just' constructor are
-- evaluated. To force the values and avoid such leaks, use 'at'' instead.
--
-- /Note:/ 'Map'-like containers form a reasonable instance, but not
-- 'Array'-like ones, where you cannot satisfy the 'Lens' laws.
at :: Index m -> Lens' m (Maybe (IxValue m))
-- | Version of 'at' strict in the value inside the `Just` constructor.
--
-- Example:
--
-- >>> (at () .~ Just (error "oops") $ Nothing) `seq` ()
-- ()
--
-- >>> (at' () .~ Just (error "oops") $ Nothing) `seq` ()
-- *** Exception: oops
-- ...
--
-- >>> view (at ()) (Just $ error "oops") `seq` ()
-- ()
--
-- >>> view (at' ()) (Just $ error "oops") `seq` ()
-- *** Exception: oops
-- ...
--
-- It also works as expected for other data structures:
--
-- >>> (at 1 .~ Just (error "oops") $ Map.empty) `seq` ()
-- ()
--
-- >>> (at' 1 .~ Just (error "oops") $ Map.empty) `seq` ()
-- *** Exception: oops
-- ...
at' :: At m => Index m -> Lens' m (Maybe (IxValue m))
at' k = at k % iso f f
where
f = \case
Just !x -> Just x
Nothing -> Nothing
{-# INLINE at' #-}
-- | Delete the value associated with a key in a 'Map'-like container
--
-- @
-- 'sans' k = 'at' k 'Optics.Operators..~' Nothing
-- @
sans :: At m => Index m -> m -> m
sans k = set (at k) Nothing
{-# INLINE sans #-}
instance At (Maybe a) where
at () = lensVL id
{-# INLINE at #-}
instance At (IntMap a) where
#if MIN_VERSION_containers(0,5,8)
at k = lensVL $ \f -> IntMap.alterF f k
#else
at k = lensVL $ \f m ->
let mv = IntMap.lookup k m
in f mv <&> \r -> case r of
Nothing -> maybe m (const (IntMap.delete k m)) mv
Just v' -> IntMap.insert k v' m
#endif
{-# INLINE at #-}
instance Ord k => At (Map k a) where
#if MIN_VERSION_containers(0,5,8)
at k = lensVL $ \f -> Map.alterF f k
#else
at k = lensVL $ \f m ->
let mv = Map.lookup k m
in f mv <&> \r -> case r of
Nothing -> maybe m (const (Map.delete k m)) mv
Just v' -> Map.insert k v' m
#endif
{-# INLINE at #-}
instance At IntSet where
at k = lensVL $ \f m ->
let mv = if IntSet.member k m
then Just ()
else Nothing
in f mv <&> \r -> case r of
Nothing -> maybe m (const (IntSet.delete k m)) mv
Just () -> IntSet.insert k m
{-# INLINE at #-}
instance Ord k => At (Set k) where
at k = lensVL $ \f m ->
let mv = if Set.member k m
then Just ()
else Nothing
in f mv <&> \r -> case r of
Nothing -> maybe m (const (Set.delete k m)) mv
Just () -> Set.insert k m
{-# INLINE at #-}
----------------------------------------
-- Internal
ixListVL :: Int -> AffineTraversalVL' [a] a
ixListVL k point f xs0 =
if k < 0
then point xs0
else let go [] _ = point []
go (a:as) 0 = f a <&> (:as)
go (a:as) i = (a:) <$> (go as $! i - 1)
in go xs0 k
{-# INLINE ixListVL #-}
-- $setup
-- >>> import Optics.Core