{- Data/Metrology/Combinators.hs

   The units Package
   Copyright (c) 2013 Richard Eisenberg
   rae@cs.brynmawr.edu

   This file defines combinators to build more complex units and dimensions from simpler ones.
-}

{-# LANGUAGE TypeOperators, TypeFamilies, UndecidableInstances,
             ScopedTypeVariables, DataKinds, FlexibleInstances,
             ConstraintKinds, CPP #-}

#if __GLASGOW_HASKELL__ >= 711
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
#endif

module Data.Metrology.Combinators where

import Data.Singletons ( SingI, sing )

import Data.Metrology.Dimensions
import Data.Metrology.Units
import Data.Metrology.Factor
import Data.Metrology.Z
import Data.Type.Equality
import Data.Metrology.LCSU

infixl 7 :*
-- | Multiply two units to get another unit.
-- For example: @type MetersSquared = Meter :* Meter@
data u1 :* u2 = u1 :* u2

instance (Dimension d1, Dimension d2) => Dimension (d1 :* d2) where
  type DimFactorsOf (d1 :* d2)
    = Normalize ((DimFactorsOf d1) @+ (DimFactorsOf d2))

instance (Unit u1, Unit u2) => Unit (u1 :* u2) where

  -- we override the default conversion lookup behavior
  type BaseUnit (u1 :* u2) = Canonical
  type DimOfUnit (u1 :* u2) = DimOfUnit u1 :* DimOfUnit u2
  conversionRatio :: (u1 :* u2) -> Rational
conversionRatio u1 :* u2
_ = Rational
forall a. HasCallStack => a
undefined -- this should never be called

  type UnitFactorsOf (u1 :* u2)
    = Normalize ((UnitFactorsOf u1) @+ (UnitFactorsOf u2))
  canonicalConvRatio :: (u1 :* u2) -> Rational
canonicalConvRatio u1 :* u2
_ = u1 -> Rational
forall unit. Unit unit => unit -> Rational
canonicalConvRatio (u1
forall a. HasCallStack => a
undefined :: u1) Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
*
                         u2 -> Rational
forall unit. Unit unit => unit -> Rational
canonicalConvRatio (u2
forall a. HasCallStack => a
undefined :: u2)

type instance DefaultUnitOfDim (d1 :* d2) =
  DefaultUnitOfDim d1 :* DefaultUnitOfDim d2

instance (Show u1, Show u2) => Show (u1 :* u2) where
  show :: (u1 :* u2) -> String
show u1 :* u2
_ = u1 -> String
forall a. Show a => a -> String
show (u1
forall a. HasCallStack => a
undefined :: u1) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" " String -> ShowS
forall a. [a] -> [a] -> [a]
++ u2 -> String
forall a. Show a => a -> String
show (u2
forall a. HasCallStack => a
undefined :: u2)


infixl 7 :/
-- | Divide two units to get another unit
data u1 :/ u2 = u1 :/ u2

instance (Dimension d1, Dimension d2) => Dimension (d1 :/ d2) where
  type DimFactorsOf (d1 :/ d2)
    = Normalize ((DimFactorsOf d1) @- (DimFactorsOf d2))

instance (Unit u1, Unit u2) => Unit (u1 :/ u2) where
  type BaseUnit (u1 :/ u2) = Canonical
  type DimOfUnit (u1 :/ u2) = DimOfUnit u1 :/ DimOfUnit u2
  conversionRatio :: (u1 :/ u2) -> Rational
conversionRatio u1 :/ u2
_ = Rational
forall a. HasCallStack => a
undefined -- this should never be called
  type UnitFactorsOf (u1 :/ u2)
    = Normalize ((UnitFactorsOf u1) @- (UnitFactorsOf u2))
  canonicalConvRatio :: (u1 :/ u2) -> Rational
canonicalConvRatio u1 :/ u2
_ = u1 -> Rational
forall unit. Unit unit => unit -> Rational
canonicalConvRatio (u1
forall a. HasCallStack => a
undefined :: u1) Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/
                         u2 -> Rational
forall unit. Unit unit => unit -> Rational
canonicalConvRatio (u2
forall a. HasCallStack => a
undefined :: u2)

type instance DefaultUnitOfDim (d1 :/ d2) =
  DefaultUnitOfDim d1 :/ DefaultUnitOfDim d2

instance (Show u1, Show u2) => Show (u1 :/ u2) where
  show :: (u1 :/ u2) -> String
show u1 :/ u2
_ = u1 -> String
forall a. Show a => a -> String
show (u1
forall a. HasCallStack => a
undefined :: u1) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"/" String -> ShowS
forall a. [a] -> [a] -> [a]
++ u2 -> String
forall a. Show a => a -> String
show (u2
forall a. HasCallStack => a
undefined :: u2)


infixr 8 :^
-- | Raise a unit to a power, known at compile time
data unit :^ (power :: Z) = unit :^ Sing power

instance Dimension dim => Dimension (dim :^ power) where
  type DimFactorsOf (dim :^ power)
    = Normalize ((DimFactorsOf dim) @* power)

instance (Unit unit, SingI power) => Unit (unit :^ power) where
  type BaseUnit (unit :^ power) = Canonical
  type DimOfUnit (unit :^ power) = DimOfUnit unit :^ power
  conversionRatio :: (unit :^ power) -> Rational
conversionRatio unit :^ power
_ = Rational
forall a. HasCallStack => a
undefined

  type UnitFactorsOf (unit :^ power)
    = Normalize ((UnitFactorsOf unit) @* power)
  canonicalConvRatio :: (unit :^ power) -> Rational
canonicalConvRatio unit :^ power
_ = unit -> Rational
forall unit. Unit unit => unit -> Rational
canonicalConvRatio (unit
forall a. HasCallStack => a
undefined :: unit) Rational -> Int -> Rational
forall a b. (Fractional a, Integral b) => a -> b -> a
^^ (Sing power -> Int
forall (z :: Z). Sing z -> Int
szToInt (Sing power
forall k (a :: k). SingI a => Sing a
sing :: Sing power))

type instance DefaultUnitOfDim (d :^ z) = DefaultUnitOfDim d :^ z

instance (Show u1, SingI power) => Show (u1 :^ (power :: Z)) where
  show :: (u1 :^ power) -> String
show u1 :^ power
_ = u1 -> String
forall a. Show a => a -> String
show (u1
forall a. HasCallStack => a
undefined :: u1) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"^" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show (Sing power -> Int
forall (z :: Z). Sing z -> Int
szToInt (Sing power
forall k (a :: k). SingI a => Sing a
sing :: Sing power))


infixr 9 :@
-- | Multiply a conversion ratio by some constant. Used for defining prefixes.
data prefix :@ unit = prefix :@ unit

-- | A class for user-defined prefixes
class UnitPrefix prefix where
  -- | This should return the desired multiplier for the prefix being defined.
  -- This function must /not/ inspect its argument.
  multiplier :: Fractional f => prefix -> f

instance ( (unit == Canonical) ~ False
         , Unit unit
         , UnitPrefix prefix ) => Unit (prefix :@ unit) where
  type BaseUnit (prefix :@ unit) = unit
  conversionRatio :: (prefix :@ unit) -> Rational
conversionRatio prefix :@ unit
_ = prefix -> Rational
forall prefix f. (UnitPrefix prefix, Fractional f) => prefix -> f
multiplier (prefix
forall a. HasCallStack => a
undefined :: prefix)

instance (Show prefix, Show unit) => Show (prefix :@ unit) where
  show :: (prefix :@ unit) -> String
show prefix :@ unit
_ = prefix -> String
forall a. Show a => a -> String
show (prefix
forall a. HasCallStack => a
undefined :: prefix) String -> ShowS
forall a. [a] -> [a] -> [a]
++ unit -> String
forall a. Show a => a -> String
show (unit
forall a. HasCallStack => a
undefined :: unit)