{- |
Combination of @(==)@ and @if then else@
that can be instantiated for more types than @Eq@
or can be instantiated in a way
that allows more defined results (\"more total\" functions):

* Reader like types for representing a context
  like 'Number.ResidueClass.Reader'

* Expressions in an EDSL

* More generally every type based on an applicative functor

* Tuples and Vector types

* Positional and Peano numbers,
  a common prefix of two numbers can be emitted
  before the comparison is done.
  (This could also be done with an overloaded 'if',
   what we also do not have.)
-}
module Algebra.EqualityDecision where

import qualified NumericPrelude.Elementwise as Elem
import Control.Applicative (Applicative(pure, (<*>)), )
import Data.Tuple.HT (fst3, snd3, thd3, )
import Data.List (zipWith4, )


{- |
For atomic types this could be a superclass of 'Eq'.
However for composed types like tuples, lists, functions
we do elementwise comparison
which is incompatible with the complete comparison performed by '(==)'.
-}
class C a where
   {- |
   It holds

   > (a ==? b) eq noteq  ==  if a==b then eq else noteq

   for atomic types where the right hand side can be defined.
   -}
   (==?) :: a -> a -> a -> a -> a



{-# INLINE deflt #-}
deflt :: Eq a => a -> a -> a -> a -> a
deflt a b eq noteq =
   if a==b then eq else noteq



instance C Int where
   {-# INLINE (==?) #-}
   (==?) = deflt

instance C Integer where
   {-# INLINE (==?) #-}
   (==?) = deflt

instance C Float where
   {-# INLINE (==?) #-}
   (==?) = deflt

instance C Double where
   {-# INLINE (==?) #-}
   (==?) = deflt

instance C Bool where
   {-# INLINE (==?) #-}
   (==?) = deflt

instance C Ordering where
   {-# INLINE (==?) #-}
   (==?) = deflt



{-# INLINE element #-}
element ::
   (C x) =>
   (v -> x) -> Elem.T (v,v,v,v) x
element f =
   Elem.element (\(x,y,eq,noteq) -> (f x ==? f y) (f eq) (f noteq))

{-# INLINE (<*>.==?) #-}
(<*>.==?) ::
   (C x) =>
   Elem.T (v,v,v,v) (x -> a) -> (v -> x) -> Elem.T (v,v,v,v) a
(<*>.==?) f acc =
   f <*> element acc


instance (C a, C b) => C (a,b) where
   {-# INLINE (==?) #-}
   (==?) = Elem.run4 $ pure (,) <*>.==?  fst <*>.==?  snd

instance (C a, C b, C c) => C (a,b,c) where
   {-# INLINE (==?) #-}
   (==?) = Elem.run4 $ pure (,,) <*>.==?  fst3 <*>.==?  snd3 <*>.==?  thd3

instance C a => C [a] where
   {-# INLINE (==?) #-}
   (==?) = zipWith4 (==?)

instance (C a) => C (b -> a) where
   {-# INLINE (==?) #-}
   (==?) x y eq noteq c  =  (x c ==? y c) (eq c) (noteq c)