exchangealgebra-0.6.0.0: src/ExchangeAlgebra/Algebra/Base/Representation.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE Strict #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstrainedClassMethods #-}
{-# LANGUAGE DeriveGeneric #-}
-- | Generic bases and Hat labels for exchange algebra.
module ExchangeAlgebra.Algebra.Base.Representation where
import ExchangeAlgebra.Algebra.Element.Representation
import Data.Time (Day, TimeOfDay)
import GHC.Stack (HasCallStack, callStack, prettyCallStack)
import qualified Data.Binary as Binary
import Control.DeepSeq (NFData(..))
-- | Raise an error with the current call stack appended.
customError :: HasCallStack => String -> a
customError msg = error (msg ++ "\nCallStack:\n" ++ prettyCallStack callStack)
------------------------------------------------------------------
-- * Base conditions
------------------------------------------------------------------
-- ** Base
------------------------------------------------------------------
{- | Base class definition.
Any type that is an instance of this class qualifies as a base.
-}
class (Element a) => BaseClass a where
compareBase :: a -> a -> Ordering
compareBase = compareElement
instance (Element e1, Element e2)
=> BaseClass (e1, e2) where
instance (Element e1, Element e2, Element e3)
=> BaseClass (e1, e2, e3) where
instance (Element e1, Element e2, Element e3, Element e4)
=> BaseClass (e1, e2, e3, e4) where
instance (Element e1, Element e2, Element e3, Element e4, Element e5)
=> BaseClass (e1, e2, e3, e4, e5) where
instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6)
=> BaseClass (e1, e2, e3, e4, e5, e6) where
-- 7-tuple: 'Element'/'AxisDecompose' already provide 7-tuple instances; this
-- closes the gap so every Element tuple arity is also usable as a base.
instance (Element e1, Element e2, Element e3, Element e4, Element e5, Element e6, Element e7)
=> BaseClass (e1, e2, e3, e4, e5, e6, e7) where
------------------------------------------------------------------
-- ** HatBase
------------------------------------------------------------------
-- | Type class for bases with a Hat component. Provides functionality to decompose and
-- compose a base into its Hat part and BasePart. Manages the Hat (decrease) \/
-- Not (increase) label at the base level in exchange algebra. Note that Hat\/Not
-- is __not__ the debit\/credit distinction: the side of a posting is determined
-- by the account division /together with/ this label (see
-- 'ExchangeAlgebra.Accounting.Exchange.whichSide' — an
-- account sits on its home side when 'Not' and on the opposite side when v'Hat').
class (BaseClass a, BaseClass (BasePart a), AxisDecompose (BasePart a)) => HatBaseClass a where
-- | The type of the base part excluding the Hat.
type BasePart a
-- | Extract the base part excluding the Hat. Complexity: O(1)
base :: (BaseClass (BasePart a)) => a -> BasePart a
-- | Extract the Hat part. Complexity: O(1)
hat :: a -> Hat
-- | Reconstruct a base from a Hat and a BasePart. Complexity: O(1)
merge :: Hat -> BasePart a -> a
-- | Convert to the Hat side. Complexity: O(1)
toHat :: a -> a
-- | Convert to the Not side. Complexity: O(1)
toNot :: a -> a
-- | Reverse Hat/Not. Complexity: O(1)
revHat :: a -> a
-- | Test whether the base is Hat. Complexity: O(1)
isHat :: a -> Bool
-- | Test whether the base is Not. Complexity: O(1)
isNot :: a -> Bool
-- | Compare bases with Hat. Defaults to 'compareBase'. Complexity: O(k)
compareHatBase :: a -> a -> Ordering
compareHatBase = compareBase
------------------------------------------------------------------
-- | Hat definition
data Hat = Hat
| Not
| HatNot
deriving (Enum, Eq, Ord, Show, Generic)
instance NFData Hat
instance Hashable Hat where
instance Binary.Binary Hat
instance Element Hat where
wildcard = HatNot
{-# INLINE equal #-}
equal Hat Hat = True
equal Hat Not = False
equal Not Hat = False
equal Not Not = True
equal _ _ = True
instance BaseClass Hat where
-- | A one-constructor basis type whose only value is its own wildcard.
data BaseForSingleHat = BaseForSingleHat
deriving (Eq,Ord,Generic)
instance NFData BaseForSingleHat
instance Show BaseForSingleHat where
show _ = ""
instance Hashable BaseForSingleHat where
instance Binary.Binary BaseForSingleHat
instance Element BaseForSingleHat where
wildcard = BaseForSingleHat
equal _ _ = True
instance BaseClass BaseForSingleHat where
instance HatBaseClass Hat where
type BasePart Hat = BaseForSingleHat
hat = id
base _ = BaseForSingleHat
-- NB. 'merge'\/'revHat'\/'isHat' below match only @Hat@ and @Not@. The third
-- v'Hat' constructor @HatNot@ is the formalization-only wildcard state (the
-- paper convention is the 2-state Hat\/Not; see CLAUDE.md "HatNot wildcard").
-- These methods are never invoked on a @HatNot@ label by library code, so the
-- non-exhaustive @-Wincomplete-patterns@ here is by design (audited). Adding a
-- @HatNot@ case would change behavior (turn the pattern-match failure into a
-- different error), so it is intentionally left as-is rather than masked.
merge Hat _ = Hat
merge Not _ = Not
{-# INLINE toHat #-}
toHat _ = Hat
{-# INLINE toNot #-}
toNot _ = Not
{-# INLINE revHat #-}
revHat Hat = Not
revHat Not = Hat
{-# INLINE isHat #-}
isHat Hat = True
isHat Not = False
{-# INLINE isNot #-}
isNot = not . isHat
------------------------------------------------------------------
-- | Base with Hat. Attaches a Hat (decrease) / Not (increase) label to a base
-- element such as a currency unit. Use the constructor @(:<)@ as in @Hat :< Yen@.
data HatBase a where
(:<) :: (BaseClass a) => {_hat :: Hat, _base :: a } -> HatBase a
instance (BaseClass a, NFData a) => NFData (HatBase a) where
rnf (hatValue :< baseValue) = rnf hatValue `seq` rnf baseValue
instance (BaseClass a, Binary.Binary a) => Binary.Binary (HatBase a) where
put (h :< b) = Binary.put h >> Binary.put b
get = (:<) <$> Binary.get <*> Binary.get
instance Show (HatBase a) where
show (h :< b) = show h ++ ":<" ++ show b
instance Eq (HatBase a) where
{-# INLINE (==) #-}
(==) (h1 :< b1) (h2 :< b2) = h1 == h2 && b1 == b2
{-# INLINE (/=) #-}
(/=) x y = not (x == y)
instance Ord (HatBase a) where
{-# INLINE compare #-}
compare (h :< b) (h' :< b') =
case compare b b' of
EQ -> compare h h'
x -> x
instance (BaseClass a) => Hashable (HatBase a) where
hashWithSalt salt (h:<b) = salt `hashWithSalt` h
`hashWithSalt` b
-- | Element (HatBase a)
-- haveWildcard
-- >>> haveWildcard (HatNot:<Amount :: HatBase CountUnit)
-- True
--
-- (.==)
-- >>> Not:<(Yen, Dollar) == Not:<(Yen,(.#))
-- False
--
-- >>> Not:<(Yen, Dollar) .== Not:<(Yen,(.#))
-- True
--
-- compareElement
-- >>> type Test = HatBase CountUnit
-- >>> compareHatBase (Not:<Amount :: Test) (Not:<(.#) :: Test)
-- EQ
--
-- ignoreWildcard
-- >>> ignoreWildcard (Not:<(Yen,Dollar)) (Hat:<(Yen,Amount))
-- Hat:<(Yen,Amount)
--
-- >>> ignoreWildcard (Not:<(Yen,Dollar)) (Hat:<(Yen,(.#)))
-- Hat:<(Yen,Dollar)
--
-- >>> ignoreWildcard (Not:<(Yen,(.#))) (HatNot:<((.#),Amount))
-- Not:<(Yen,Amount)
instance (BaseClass a) => Element (HatBase a) where
wildcard = HatNot :<wildcard
haveWildcard (h:<b)
= isWildcard h
|| haveWildcard b
{-# INLINE equal #-}
equal (h1:<b1) (h2:<b2) = h1 .== h2 && b1 .== b2
ignoreWildcard (h1:<b1) (h2:<b2)
= (ignoreWildcard h1 h2) :< (ignoreWildcard b1 b2)
compareElement (h1:<b1) (h2:<b2)
= case compareElement b1 b2 of
EQ -> compareElement h1 h2
x -> x
instance (BaseClass a) => BaseClass (HatBase a) where
instance (BaseClass a, AxisDecompose a) => HatBaseClass (HatBase a) where
type BasePart (HatBase a) = a
hat = _hat
base = _base
merge = (:<)
{-# INLINE toHat #-}
toHat (_:<b) = Hat:<b
{-# INLINE toNot #-}
toNot (_:<b) = Not:<b
{-# INLINE revHat #-}
revHat (Hat :< b) = Not :< b
revHat (Not :< b) = Hat :< b
{-# INLINE isHat #-}
isHat (Hat :< _) = True
isHat (Not :< _) = False
isHat (HatNot :< _) = customError "called HatNot"
{-# INLINE isNot #-}
isNot = not . isHat
instance BaseClass Name where
-- *** CountUnit only (redundant algebra base)
instance BaseClass CountUnit where
-- *** Day only (redundant algebra base)
instance BaseClass Day where
-- *** TimeOfDay only (redundant algebra base)
instance BaseClass TimeOfDay where