content: {- 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 _ = undefined -- this should never be called
  
    type UnitFactorsOf (u1 :* u2)
      = Normalize ((UnitFactorsOf u1) @+ (UnitFactorsOf u2))
    canonicalConvRatio _ = canonicalConvRatio (undefined :: u1) *
                           canonicalConvRatio (undefined :: u2)
  
  type instance DefaultUnitOfDim (d1 :* d2) =
    DefaultUnitOfDim d1 :* DefaultUnitOfDim d2
  
  instance (Show u1, Show u2) => Show (u1 :* u2) where
    show _ = show (undefined :: u1) ++ " " ++ show (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 _ = undefined -- this should never be called
    type UnitFactorsOf (u1 :/ u2)
      = Normalize ((UnitFactorsOf u1) @- (UnitFactorsOf u2))
    canonicalConvRatio _ = canonicalConvRatio (undefined :: u1) /
                           canonicalConvRatio (undefined :: u2)
  
  type instance DefaultUnitOfDim (d1 :/ d2) =
    DefaultUnitOfDim d1 :/ DefaultUnitOfDim d2
  
  instance (Show u1, Show u2) => Show (u1 :/ u2) where
    show _ = show (undefined :: u1) ++ "/" ++ show (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 _ = undefined
  
    type UnitFactorsOf (unit :^ power)
      = Normalize ((UnitFactorsOf unit) @* power)
    canonicalConvRatio _ = canonicalConvRatio (undefined :: unit) ^^ (szToInt (sing :: Sing power))
  
  type instance DefaultUnitOfDim (d :^ z) = DefaultUnitOfDim d :^ z
  
  instance (Show u1, SingI power) => Show (u1 :^ (power :: Z)) where
    show _ = show (undefined :: u1) ++ "^" ++ show (szToInt (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 _ = multiplier (undefined :: prefix)
  
  instance (Show prefix, Show unit) => Show (prefix :@ unit) where
    show _ = show (undefined :: prefix) ++ show (undefined :: unit)
  