convert-units-0: src/Data/Units/Base/Prefix.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE InstanceSigs #-}
--------------------------------------------------------------------------------
-- |
--
-- Module : Data.Units.Base.Prefix
-- Description : Unit prefix for a system of units
-- Copyright : (c) Alice Rixte 2025
-- License : BSD 3
-- Maintainer : alice.rixte@u-bordeaux.fr
-- Stability : unstable
-- Portability : non-portable (GHC extensions)
--
-- Provides a way to define prefixes for any system of units.
--
--------------------------------------------------------------------------------
module Data.Units.Base.Prefix where
import GHC.TypeError
import Data.Units.Base.System
import Data.Units.Base.Convert
-- | A unit prefix, like Kilo, Milli, etc.
type Prefix = Unit -> Unit
-- | The application of a prefix to a unit must always be a unit.
class (forall (u :: Unit). IsUnit u => IsUnit (p u))
=> IsPrefix (p :: Prefix)
instance (forall (u :: Unit). IsUnit u => IsUnit (p u))
=> IsPrefix (p :: Prefix)
-- | A prefix that has a conversion factor.
--
class (Fractional a, IsPrefix p) => PrefixFactor (p :: Prefix) a where
-- | Prefix conversion factor from the prefixed unit to the corresponding
-- standard unit
--
-- >>> prefixFactor @Kilo
-- 1000.0
--
prefixFactor :: a
-- | Prefixes that can be shown as a string, or as a type error message.
class IsPrefix p => ShowPrefix (p :: Prefix) where
{-# MINIMAL showPrefix | showsPrefixPrec #-}
-- | Allows to print units in conversion error messages
--
-- >>> type ShowPrefix Kilo = "k"
--
type ShowPrefixType p :: ErrorMessage
-- | Convert a prefix to a readable string
--
-- @'showsPrefixPrec'@ should satisfy the law :
--
-- @showsPrefixPrec d x r ++ s == showsPrec d x (r ++ s)@
--
showsPrefixPrec :: Int -> ShowS
showsPrefixPrec _ = (showPrefix @p ++)
-- | Convert a prefix to a string representing its type.
--
-- >>> showPrefix @Kilo
-- "Kilo"
--
showPrefix :: String
showPrefix = showsPrefix @p ""
-- | Same as @'showsPrefixPrec'@ but for pretty printing.
--
-- @'prettysPrefixPrec'@ should satisfy the law :
--
-- @prettysPrefixPrec d x r ++ s == prettysPrec d x (r ++ s)@
--
prettysPrefixPrec :: Int -> ShowS
prettysPrefixPrec _ = (prettyPrefix @p ++)
-- | Same as @'showPrefix'@ but for pretty printing
--
-- >>> prettyPrefix @Kilo
-- "k"
prettyPrefix :: String
prettyPrefix = prettysPrefix @p ""
-- | Equivalent to 'showsPrefixPrec' with a precedence of 0.
showsPrefix :: forall p. ShowPrefix p => ShowS
showsPrefix = showsPrefixPrec @p 0
-- | Equivalent to 'prettysPrefixPrec' with a precedence of 0.
prettysPrefix :: forall p. ShowPrefix p => ShowS
prettysPrefix = prettysPrefixPrec @p 0
-- | A prefix that can represent any prefix.
--
-- This can be used with the `deriving via` mechanism to derive some of the
-- prefix instances.
--
newtype MetaPrefix (p :: Prefix) (u :: Unit) a = MetaPrefix (p u a)
deriving Show via (MetaUnit (p u) a)
instance PrefixFactor p a => PrefixFactor (MetaPrefix p) a where
prefixFactor = prefixFactor @p
{-# INLINE prefixFactor #-}
instance
(PrefixFactor p a, ConversionFactor u a, BaseUnitOf (p u) ~ BaseUnitOf u)
=> ConversionFactor (MetaPrefix p u) a where
factor = prefixFactor @p * factor @u
{-# INLINE factor #-}
instance
(PrefixFactor p a, ConvertibleUnit u a, BaseUnitOf (p u) ~ BaseUnitOf u)
=> ConvertibleUnit (MetaPrefix p u) a where
toBaseUnit (MetaPrefix a) = prefixToBaseUnit @p @u a
{-# INLINE toBaseUnit #-}
fromBaseUnit a = MetaPrefix $ prefixFromBaseUnit @p @u a
{-# INLINE fromBaseUnit #-}
-- | Convert a prefixed unit to the corresponding standard unit.
--
prefixToBaseUnit :: forall (p :: Prefix) (u :: Unit) a.
(PrefixFactor p a, ConvertibleUnit u a, BaseUnitOf (p u) ~ BaseUnitOf u)
=> p u a -> BaseUnitOf u a
prefixToBaseUnit u =
toBaseUnit @u $ quantity @u (prefixFactor @p * unQuantity u)
{-# INLINE prefixToBaseUnit #-}
-- | Convert a standard unit to the corresponding prefixed unit.
--
prefixFromBaseUnit :: forall (p :: Prefix) (u :: Unit) a.
(PrefixFactor p a, ConvertibleUnit u a
, BaseUnitOf (p u) a ~ BaseUnitOf u a)
=> BaseUnitOf (p u) a -> p u a
prefixFromBaseUnit a = quantity $ unQuantity (fromBaseUnit @u a) / prefixFactor @p
{-# INLINE prefixFromBaseUnit #-}
instance ShowPrefix p => ShowPrefix (MetaPrefix p) where
type ShowPrefixType (MetaPrefix p) = ShowPrefixType p
showsPrefixPrec = showsPrefixPrec @p
showPrefix = showPrefix @p
prettysPrefixPrec = prettysPrefixPrec @p
prettyPrefix = prettyPrefix @p
instance (IsPrefix p, IsUnit u)
=> IsUnit (MetaPrefix p u) where
type DimOf (MetaPrefix p u) = DimOf u
instance (ShowPrefix p, ShowUnit u)
=> ShowUnit (MetaPrefix p u) where
type ShowUnitType (MetaPrefix p u) = ShowPrefixType p :<>: ShowUnitType u
showsUnitPrec d = showParen (d > 10) $
showsPrefix @p . showString " " . showsUnitPrec @u 11
prettysUnitPrec d = showParen (d > 10) $
prettysPrefix @p . prettysUnit @u