-- | Provides the 'Normalize' typeclass.
--
-- @since 0.1
module Data.Bytes.Class.Normalize
  ( Normalize (..),
  )
where

-- $setup
-- >>> import Data.Bytes (Bytes (..), SomeSize (..), hideSize)
-- >>> import Data.Bytes.Size (Size (..))

-- | Used for normalizing bytes @b@ such that
--
-- \[
--  1 \le \text{normalize}(b) < 1000 \iff 1\text{ B} \le b < 1000\text{ Y}.
-- \]
--
-- In the strictest sense, \(\textrm{normalize} : \mathcal{C} \rightarrow \mathcal{C}\)
-- is not a homomorphism, as the combination of two normalized values may
-- itself not be normalized.
--
-- However, because the normalized units varies with the value, @normalize@
-- always returns a type that existentially quantifies the size
-- (e.g. 'Data.Bytes.SomeSize'). 'Eq' for these types is defined in
-- terms of an equivalence class that takes units into account e.g.
-- @1 P = 1,000 T = 1,000,000 G ...@. Viewed this way, @normalize@ is actually
-- an /isomorphism/, as it is essentially a no-op, never leaving the
-- equivalence class.
--
-- This means we can happily mix normalization with different functions
-- without worrying about the order. The only requirement we have is that
-- such functions respect substitution:
--
-- \[ x = y \implies f(x) = f(y). \]
--
-- This is certainly true for all the usual mathematical operations we would
-- normally use, e.g., 'Numeric.Algebra.Additive.AGroup.AGroup' addition,
-- 'Numeric.Algebra.Module' scalar multiplication. On
-- the other hand, any functions that inspect the underlying numeric value or
-- Bytes types could easily break this law. As such they should be treated
-- with suspicion, at least when used in conjunction with 'normalize'.
--
-- The other consideration we must keep in mind is that the final result of a
-- series of computations may not be normalized. If this is desired, then
-- 'normalize' should be the /last/ operation performed. Using 'normalize' in
-- the middle would not cause any harm (other than, perhaps, impacting
-- efficiency), but it would not guarantee the final result is normalized.
--
-- @since 0.1
class Normalize a where
  -- | @since 0.1
  type Norm a

  -- | Normalizes the value.
  --
  -- ==== __Examples__
  --
  -- >>> let bytes = MkBytes 5000 :: Bytes 'M Int
  -- >>> normalize bytes
  -- MkSomeSize SG (MkBytes 5)
  --
  -- >>> let bytes = hideSize (MkBytes 0.01 :: Bytes 'T Float)
  -- >>> normalize bytes
  -- MkSomeSize SG (MkBytes 10.0)
  --
  -- @since 0.1
  normalize :: a -> Norm a