data-as-0.0.0.2: Data/As.hs
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-|
Module : Data.As
Description : Simple extensible sum
Copyright : (c) incertia, 2020
License : MIT
Maintainer : incertia@incertia.net
Stability : experimental
Portability : portable
This module provides the 'As' class which is a multi parameter classy prism,
much like how 'Data.Has.Has' is a multi parameter classy lens.
We have the following primary use case for 'As'.
@
-- some library code
throwE :: (As e err, MonadError err m) => e -> m ()
throwE = throwError . review asPrism
@
-}
module Data.As
( As(..)
) where
import Data.Functor.Const
(Const(..))
import Data.Functor.Identity
(Identity(..))
import Data.Monoid
(First(..))
import Data.Profunctor
(Profunctor, Choice(..), dimap, right')
import Data.Profunctor.Unsafe
((.#), (#.))
import Data.Void
(Void, absurd)
import Text.Read
(readMaybe)
-- inline our own Tagged
newtype Tagged s b = Tagged { unTagged :: b }
instance Profunctor Tagged where
dimap _ f (Tagged s) = Tagged (f s)
instance Choice Tagged where
left' (Tagged b) = Tagged (Left b)
right' (Tagged b) = Tagged (Right b)
-- inline local Prism type so we don't depend on lens
-- unfortunately this means we need to depend on profunctor
type Prism t a = forall p f. (Choice p, Applicative f) => p a (f a) -> p t (f t)
-- these definitions are ripped directly from lens
-- | A typeclass for extensible sums.
--
-- The provided instances were inspired from the lens library.
--
-- Making your own instances when you actually depend on lens should be as easy
-- as @instance As Foo Bar where asPrism = _Foo@.
class As a t where
{-# MINIMAL previewer, reviewer | asPrism #-}
previewer :: t -> Maybe a
previewer = getFirst #. getConst #. asPrism (Const #. First #. Just)
reviewer :: a -> t
reviewer = runIdentity #. unTagged #. asPrism .# Tagged .# Identity
asPrism :: Prism t a
asPrism = dimap (\a -> maybe (Left a) Right (previewer a)) (either pure (fmap reviewer)) . right'
modifier :: (a -> a) -> t -> t
modifier f = runIdentity . asPrism (Identity . f)
-- some instances based on the lens library
instance As a a where
asPrism = id
{-# INLINABLE asPrism #-}
instance As a (Maybe a) where
previewer = id
{-# INLINABLE previewer #-}
reviewer = Just
{-# INLINABLE reviewer #-}
instance As () (Maybe a) where
previewer ma = case ma of
Nothing -> Just ()
Just _ -> Nothing
{-# INLINABLE previewer #-}
reviewer () = Nothing
{-# INLINABLE reviewer #-}
instance As a (Either a b) where
previewer eab = case eab of
Left a -> Just a
Right _ -> Nothing
{-# INLINABLE previewer #-}
reviewer = Left
{-# INLINABLE reviewer #-}
instance As b (Either a b) where
previewer eab = case eab of
Right b -> Just b
Left _ -> Nothing
{-# INLINABLE previewer #-}
reviewer = Right
{-# INLINABLE reviewer #-}
instance (Read a, Show a) => As a String where
previewer = readMaybe
{-# INLINABLE previewer #-}
reviewer = show
{-# INLINABLE reviewer #-}
instance As Void a where
previewer _ = Nothing
{-# INLINABLE previewer #-}
reviewer = absurd
{-# INLINABLE reviewer #-}