{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveGeneric #-}
module Gauge.Optional
( Optional
, toOptional
, unOptional
, OptionalTag(..)
, isOmitted
, omitted
, toMaybe
, fromMaybe
, map
, both
) where
import Prelude hiding (map)
import Data.Int
import Data.Word
import Data.Data
import GHC.Generics
import Basement.Compat.CallStack
newtype Optional a = Optional { unOptional :: a }
deriving (Eq, Show, Read, Typeable, Data, Generic)
class OptionalTag a where
optionalTag :: a
isOptionalTag :: a -> Bool
instance OptionalTag Int64 where
optionalTag = minBound
isOptionalTag = (==) optionalTag
instance OptionalTag Word64 where
optionalTag = maxBound
isOptionalTag = (==) optionalTag
instance OptionalTag Double where
optionalTag = -1/0
isOptionalTag d = isInfinite d || isNaN d
toOptional :: (HasCallStack, OptionalTag a) => String -> a -> Optional a
toOptional ty v
| isOptionalTag v = error ("Creating an optional valid value for " ++ ty ++ " using the optional tag")
| otherwise = Optional v
{-# INLINE toOptional #-}
omitted :: OptionalTag a => Optional a
omitted = Optional optionalTag
{-# INLINE omitted #-}
isOmitted :: OptionalTag a => Optional a -> Bool
isOmitted (Optional v)
| isOptionalTag v = True
| otherwise = False
toMaybe :: OptionalTag a => Optional a -> Maybe a
toMaybe (Optional v) | isOptionalTag v = Nothing
| otherwise = Just v
{-# INLINE toMaybe #-}
fromMaybe :: (HasCallStack, OptionalTag a) => Maybe a -> Optional a
fromMaybe Nothing = Optional optionalTag
fromMaybe (Just v)
| isOptionalTag v = error "fromMaybe: creating an optional value using the optional tag"
| otherwise = Optional v
{-# INLINE fromMaybe #-}
map :: OptionalTag a => (a -> a) -> Optional a -> Optional a
map f o@(Optional v) | isOptionalTag v = o
| otherwise = Optional (f v)
{-# INLINE map #-}
both :: (HasCallStack, OptionalTag a) => (a -> a -> a) -> Optional a -> Optional a -> Optional a
both f o1 o2
| isOmitted o1 = o2
| isOmitted o2 = o1
| isOptionalTag r = error "both: creating an optional value using the optional tag"
| otherwise = Optional r
where r = f (unOptional o1) (unOptional o2)