packages feed

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