packages feed

morley-1.18.0: src/Morley/Util/Bimap.hs

-- SPDX-FileCopyrightText: 2022 Oxhead Alpha
-- SPDX-License-Identifier: LicenseRef-MIT-OA

module Morley.Util.Bimap
  ( Bimap(..)
  , empty
    -- * Optics
  , flipped
  ) where

import Prelude hiding (empty)

import Control.Lens (At(..), Index, Iso, IxValue, Ixed(..), iso)
import Data.Aeson (FromJSON(..), ToJSON(..))
import Data.Bimap qualified as Bimap
import Data.Coerce (coerce)
import GHC.Exts (IsList)

newtype Bimap a b = Bimap { unBimap :: Bimap.Bimap a b }
  deriving newtype (Show, Eq, Ord, IsList)

empty :: Bimap a b
empty = coerce Bimap.empty

type instance Index (Bimap k _) = k
type instance IxValue (Bimap _ v) = v

-- | Left-biased 'Ixed' instance.
-- It assumes the left value @a@ is the key (just like the @Ix (Map k v)@ instance).
--
-- To flip this assumption, use the 'flipped' optic.
instance (Ord k, Ord v) => Ixed (Bimap k v) where
  ix :: k -> Traversal' (Bimap k v) v
  ix k handler (Bimap bmap) =
    case Bimap.lookup k bmap of
     Just v -> handler v <&> \v' -> Bimap $ Bimap.insert k v' bmap
     Nothing -> pure $ Bimap bmap

-- | Left-biased 'At' instance.
-- It assumes the left value @a@ is the key (just like the @At (Map k v)@ instance).
--
-- To flip this assumption, use the 'flipped' optic.
instance (Ord k, Ord v) => At (Bimap k v) where
  at :: k -> Lens' (Bimap k v) (Maybe v)
  at k handler (Bimap bmap) =
    handler currentValueMaybe <&> \newValueMaybe ->
      Bimap $
        case (currentValueMaybe, newValueMaybe) of
          (Nothing, Nothing) -> bmap
          (Just _, Nothing) -> Bimap.delete k bmap
          (_, Just newValue) -> Bimap.insert k newValue bmap
    where
      currentValueMaybe = Bimap.lookup k bmap

-- | Isomorphism between @Bimap a b@ and @Bimap b a@.
flipped :: Iso (Bimap a1 b1) (Bimap a2 b2) (Bimap b1 a1) (Bimap b2 a2)
flipped = iso (coerce Bimap.twist) (coerce Bimap.twist)

instance (Ord a, Ord b, FromJSON a, FromJSON b) => FromJSON (Bimap a b) where
  parseJSON = fmap (Bimap . Bimap.fromList) . parseJSON

instance (ToJSON a, ToJSON b) => ToJSON (Bimap a b) where
  toJSON = toJSON . Bimap.toList . unBimap