{-# LANGUAGE MagicHash #-} module Noether.Equality.Tutorial where import GHC.TypeLits import Prelude hiding (Eq, (==)) import Noether.Equality {- Library definitions would probably look like this. (They might even be put in Backpack signature files or something, for, e.g. a swap-in numerically sensible set of equality strategies that uses approximate equality for everything floating-ish.) These type instance definitions can be thought of as fine-grained applications of custom deriving strategies, as alluded to in the opening wall of text. So we have 'Numeric', but also 'Composite Approximate Numeric', and so on. (More generics?) -} -- This might be read as -- | deriving instance Eq Int using strategy Numeric type instance Equality Int = Numeric testInt = 0 == (1 :: Int) -- | deriving instance Eq Double using strategy Approximate type instance Equality Double = Approximate testDouble = (t eps1, t eps2) where t = 0.0 == (0.01 :: Double) eps1 = 0.001 eps2 = 0.1 -- And so on. {- What follows are the specifications of (unique!) equality strategies for a couple of types. A user would only interact with this bit, ideally, to define equality for her own types. The types of the test* functions have intentionally been left out. Inference stays alive and well :) -} {-| 'Common' 'Approximate' is a strategy that uses the same epsilon for both slots of the tuple, going (a -> Bool, a -> Bool) ~> a -> (Bool, Bool) ~> a -> Bool Note that I can replace this with Common Numeric and have that work just fine, even though the concrete specification of equality on Double is Approximate. Defining the Equality instance does not throw away the information of the other possible equality tests, and bigger types like tuples and such can make use of any equality that the components support. -} type instance Equality (Double, Double) = Common Approximate test1 = lhs == rhs where lhs, rhs :: (Double, Double) lhs = (2.0, 2.0) rhs = (2.00001, 2.0) {- Suppose I want a newtype that is equated differently. For instance, consider a newtype-wrapped Double that compares according to Prelude equality, not the funky tolerance-ish thing above. In defining it, I can skip making it support the operations that my choice of equality strategy requires. The use of 'PreludeEq' demands an 'Eq' constraint, but 'Dbl' does not need to derive that. FIXME: Maybe the type family should make this available? The Advanced Overlap page says that getting the class instances and the type instances to agree is "just something you'll have to do", but does ConstraintKinds let us hack around that now? -} newtype Dbl = Dbl Double -- Now I can simply coerce the equality on the base type to the newtype. type instance Equality Dbl = CoerceFrom Double PreludeEq test2 = Dbl 2.0 == Dbl 2.01 {- In case of 'Eq' on a newtype-wrapped Prelude numeric type, this is a parlor trick at best, but not having to "derive" Num (or write a one-off partial implementation) is awesome: -} newtype Dbl' = Dbl' Double -- et .. magic! type instance Equality Dbl' = CoerceFrom Double Numeric test3 = Dbl' 2.0 == Dbl' 2.01 {- (I'm intentionally using 'Composite' instead of a tuple to leave that option open for auto-deriving shenanigans.) -} type instance Equality (Dbl, Dbl') = Composite (Equality Dbl) (Equality Dbl') test4 = (Dbl 2, Dbl' 2) == (Dbl 2.001, Dbl' 2) -- Let's try the Z/n equality we defined above. -- As before, define the newtype... newtype Mod (n :: Nat) = Mod Int -- "derive" the equality strategy... type instance Equality (Mod n) = CoerceFrom Int (Explicit (Modulo n)) --- and profit! test5 = a == b where a, b :: Mod 7 a = Mod 3 b = Mod 24