{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Language.Fiddle.Internal.UnitNumbers where import Data.Aeson (FromJSON (..), ToJSON (..)) import Text.Printf -- | 'NumberWithUnit' represents a numeric value associated with a unit type. -- The unit type 'u' is a phantom type, meaning it carries no runtime data -- but provides compile-time type safety for the units. -- The actual value is of type 'i', such as 'Int' or 'Integer'. newtype NumberWithUnit u i = NumberWithUnit i deriving newtype (Real, Enum, Num, Eq, Ord, Integral, PrintfArg) -- | Custom 'Show' instance for 'NumberWithUnit' to display the numeric value. instance (Show i) => Show (NumberWithUnit u i) where show (NumberWithUnit b) = show b -- | JSON serialization for 'NumberWithUnit', converting to and from JSON using -- the underlying numeric type. instance (ToJSON i) => ToJSON (NumberWithUnit u i) where toJSON (NumberWithUnit b) = toJSON b instance (FromJSON i) => FromJSON (NumberWithUnit u i) where parseJSON v = NumberWithUnit <$> parseJSON v -- | Type alias for 'NumberWithUnit' where the underlying numeric type is 'Int'. -- This is a shorthand for convenience when working with integers. type N u = NumberWithUnit u Int -- | Phantom types representing different units that can be used with -- 'NumberWithUnit'. These types carry no data but serve as compile-time -- markers for the units. data Bits data Bytes data Address data Unitless -- | 'NamedUnit' is a typeclass for units that can be represented as strings. -- It provides a 'unitName' method for displaying a formatted representation -- of the unit. class NamedUnit u where unitName :: N u -> String -- | 'NamedUnit' instance for 'Bits'. It displays the value followed by "bit" -- or "bits", depending on whether the value is 1 or not. instance NamedUnit Bits where unitName 1 = "1 bit" unitName n = show n ++ " bits" -- | 'NamedUnit' instance for 'Bytes'. It displays the value followed by "byte" -- or "bytes", depending on whether the value is 1 or not. instance NamedUnit Bytes where unitName 1 = "1 byte" unitName n = show n ++ " bytes" -- | Convert a value from 'Bits' to 'Bytes'. This returns a tuple containing the -- number of complete bytes and the remaining bits. bitsToBytes :: N Bits -> (N Bytes, N Bits) bitsToBytes (NumberWithUnit a) = let (y, i) = divMod a 8 in (NumberWithUnit y, NumberWithUnit i) -- | Convert a value from 'Bytes' to 'Bits'. This multiplies the byte value by -- 8 to get the corresponding number of bits. bytesToBits :: N Bytes -> N Bits bytesToBits (NumberWithUnit a) = NumberWithUnit (a * 8) -- | Multiplication operator for 'NumberWithUnit' values. The result retains -- the original unit 'u', while the right operand is of the 'Unitless' type. -- This operator is useful for scaling values by a unitless factor. (.*.) :: N u -> N Unitless -> N u (.*.) (NumberWithUnit a) (NumberWithUnit b) = NumberWithUnit (a * b)