{-# LANGUAGE LambdaCase, TypeFamilies, TemplateHaskell #-} ------------------------------------------------------------------------ -- | -- Module : Data.Extensible.Nullable -- Copyright : (c) Fumiaki Kinoshita 2018 -- License : BSD3 -- -- Maintainer : Fumiaki Kinoshita -- ------------------------------------------------------------------------ module Data.Extensible.Nullable ( vacancy , coinclusion , wrench , retrench , Nullable(..) , mapNullable , fromNullable) where import Control.DeepSeq (NFData) import Data.Extensible.Class import Data.Extensible.Product import Data.Extensible.Sum import Data.Extensible.Inclusion import Data.Extensible.Internal.Rig import Data.Hashable import Data.Typeable (Typeable) import Data.Extensible.Wrapper import qualified Data.Extensible.Struct as S import Data.Profunctor.Unsafe import Data.Maybe (fromMaybe) import GHC.Generics (Generic) import Language.Haskell.TH.Lift import Language.Haskell.TH (appE, conE) import Test.QuickCheck.Arbitrary -- | Wrapped Maybe newtype Nullable h x = Nullable { getNullable :: Maybe (h x) } deriving (Show, Eq, Ord, Typeable, Generic, NFData, Arbitrary, Hashable) instance Wrapper h => Wrapper (Nullable h) where type Repr (Nullable h) x = Maybe (Repr h x) _Wrapper = withIso _Wrapper $ \f g -> dimap (fmap f . getNullable) (fmap (Nullable . fmap g)) instance Semigroup (h x) => Semigroup (Nullable h x) where Nullable (Just a) <> Nullable (Just b) = Nullable (Just (a <> b)) a@(Nullable (Just _)) <> _ = a _ <> b = b instance Semigroup (h x) => Monoid (Nullable h x) where mempty = Nullable Nothing mappend = (<>) instance Lift (h a) => Lift (Nullable h a) where lift = appE (conE 'Nullable) . lift . getNullable -- | Apply a function to its content. mapNullable :: (g x -> h y) -> Nullable g x -> Nullable h y mapNullable f = Nullable #. fmap f .# getNullable {-# INLINE mapNullable #-} -- | The inverse of 'inclusion'. coinclusion :: (Include ys xs, Generate ys) => ys :& Nullable (Membership xs) coinclusion = S.hfrozen $ do s <- S.newRepeat $ Nullable Nothing hfoldrWithIndex (\i m cont -> S.set s m (Nullable $ Just i) >> cont) (return s) inclusion -- | A product filled with @'Nullable' 'Nothing'@ vacancy :: Generate xs => xs :& Nullable h vacancy = hrepeat $ Nullable Nothing -- | Extend a product and fill missing fields by 'Null'. wrench :: (Generate ys, xs ⊆ ys) => xs :& h -> ys :& Nullable h wrench xs = mapNullable (flip hlookup xs) `hmap` coinclusion {-# INLINE wrench #-} -- | Narrow the range of the sum, if possible. retrench :: (Generate ys, xs ⊆ ys) => ys :/ h -> Nullable ((:/) xs) h retrench (EmbedAt i h) = views (pieceAt i) (mapNullable (`EmbedAt`h)) coinclusion {-# INLINE retrench #-} -- | 'fromMaybe' for 'Nullable'. fromNullable :: h x -> Nullable h x -> h x fromNullable def = fromMaybe def . getNullable {-# INLINE fromNullable #-}