bjornbm / dimensional

Dimensional library variant built on Data Kinds, Closed Type Families, TypeNats (GHC 7.8+).
BSD 3-Clause "New" or "Revised" License
102 stars 15 forks source link

Using dimensions and units in type classes and families #208

Closed AtelierSnek closed 4 years ago

AtelierSnek commented 4 years ago

Say I want to write a function that necessarily takes in a quantity and outputs some other quantity (of the same type), that's easy enough:

decay :: (Fractional f, Quantity b f ~ Quantity c g) => s -> Quantity b f -> Quantity c g (By the by, am I doing this correctly?)

How would I create a type class / type family that specifies or encodes its units? The use case here is I want a type class or type family that is strictly denominated in a particular unit.

The class of all lamps, where all the functions use LuminousFlux is do-able, but what about the super-class of all things that emit a field, that can use one of LuminousFlux or MagneticFlux?

Hope that makes sense

bjornbm commented 4 years ago

Hi @AtelierFox, I may be missing your point but for decay I think you would be better off with the straightforward type:

decay :: Fractional f => s -> Quantity d f -> Quantity d f

You probably also need to be more specific about the type s. If it is a ratio then Dimensionless f might be an appropriate substitution. For example:

import Numeric.Units.Dimensional.Prelude
import qualified Prelude

decay :: Floating f => Dimensionless f -> Quantity d f -> Quantity d f
decay t x = x * exp (negate t)

Usage:

>>> decay _1 (1 *~ candela)
0.36787944117144233 cd

>>> decay (0.5 *~ one) (10 *~ weber)
6.065306597126334 m^2 kg s^-2 A^-1

If this is not what you are looking for perhaps could you provide some pseudo-code illustrating what you want to do?

AtelierSnek commented 4 years ago

The idea is I want to create a data family such that each instance defines a dimension, and operations upon it (that are true and applicable for all instances). In particular, I'm trying encode various methods of sense. Using the example of human sight, we see things by photons (which would be LuminousIntensity or LuminousFlux), but if we talk about birds, they can also detect magnetic fields (which would be MagneticFlux). I used decay as an example function but it may not be a good one. A better example is

minDetectable :: Fractional f => s -> Quantity b f

where s is the data type of the family, and the function returns the smallest quantity that is detectable by the creature:

class Sense s where
  data SenseUnit s :: *
[...]

Does that make more sense?

bjornbm commented 4 years ago

Maybe I am still not understanding what you are trying to do, but I am not sure you need data families for this, not how they would help. See the below example with (multi-parameter) type classes:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
import Numeric.Units.Dimensional.Prelude

data Human = KingArthur | SirBelvedere
data Bird = AfricanSwallow

class Sensing species d where
  minDetectable :: Fractional a => species -> Quantity d a

instance Sensing Human DLuminousFlux where
  minDetectable KingArthur = 1e-2 *~ lumen
  minDetectable SirBelvedere = 1e-1 *~ lumen

instance Sensing Bird DLuminousFlux where
  minDetectable _ = 1e-3 *~ lumen

instance Sensing Bird DMagneticFlux where
  minDetectable _ = 25000 *~ nano tesla * (1 *~ milli meter ^ pos2)

Usage:

>>> minDetectable KingArthur /~ lumen
1.0e-2

>>> minDetectable AfricanSwallow /~ weber
2.5e-11

If I am still missing the point please provide an example (in code or pseudo-code) of how you would use the code you are trying to write!

AtelierSnek commented 4 years ago

Oooh, I didn't know about multi-param type classes. That's perfect! That's exactly what I was trying to do.