{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : Data.Ord.OneLiner
-- Description : Derived methods for Semigroup.
-- Copyright : (c) Justin Le 2021
-- License : BSD-3
-- Maintainer : justin@jle.im
-- Stability : unstable
-- Portability : portable
--
-- Derived methods for 'Eq' and 'Ord', using "Generics.OneLiner" and
-- "GHC.Generics".
--
-- Can be used for any types (deriving 'Generic') where every field is an
-- instance of 'Eq' (or 'Ord').
--
-- Also includes a newtype wrapper that imbues any such data type with
-- instant 'Eq' and 'Ord' instances, which can one day be used with
-- /DerivingVia/ syntax to derive instances automatically.
--
module Data.Ord.OneLiner (
-- * Newtype wrapper
GOrd(..)
-- * Generics-derived methods
-- ** Eq
, gEquals
, gNotEquals
-- ** Ord
, gCompare
, gLTE
, gLT
, gGTE
, gGT
, gMax
, gMin
) where
import Data.Coerce
import Data.Data
import Data.Monoid
import GHC.Generics
import Generics.OneLiner
-- | If @a@ is a data type whose fields are all instances of 'Eq', then
-- @'GOrd' a@ has a 'Eq' instance.
--
-- If @a@ is a data type whose fields are all instances of 'Ord', then
-- @'GOrd' a@ has a 'Ord' instance.
--
-- Will one day be able to be used with /DerivingVia/ syntax, to derive
-- instances automatically.
--
newtype GOrd a = GOrd { getGOrd :: a }
deriving (Show, Read, Data, Generic, Functor, Foldable, Traversable)
instance ( ADT a
, Constraints a Eq
)
=> Eq (GOrd a) where
(==) = coerce (gEquals @a)
{-# INLINE (==) #-}
(/=) = coerce (gNotEquals @a)
{-# INLINE (/=) #-}
instance ( ADT a
, Constraints a Eq
, Constraints a Ord
)
=> Ord (GOrd a) where
compare = coerce (gCompare @a)
{-# INLINE compare #-}
(<=) = coerce (gLTE @a)
{-# INLINE (<=) #-}
(<) = coerce (gLT @a)
{-# INLINE (<) #-}
(>=) = coerce (gGTE @a)
{-# INLINE (>=) #-}
(>) = coerce (gGT @a)
{-# INLINE (>) #-}
max = coerce (gMax @a)
{-# INLINE max #-}
min = coerce (gMin @a)
{-# INLINE min #-}
-- | '==' implemented by using '==' between all of the
-- components, lexicographically. First compares constructors.
gEquals
:: forall a. (ADT a, Constraints a Eq)
=> a -> a -> Bool
gEquals x y = ctorIndex x == ctorIndex y
&& getAll (mzipWith @Eq (\x' -> All . (== x')) x y)
{-# INLINE gEquals #-}
-- | '/=' implemented by using '/=' between all of the
-- components, lexicographically. First compares constructors.
gNotEquals
:: forall a. (ADT a, Constraints a Eq)
=> a -> a -> Bool
gNotEquals x y = ctorIndex x /= ctorIndex y
|| getAny (mzipWith @Eq (\x' -> Any . (/= x')) x y)
{-# INLINE gNotEquals #-}
-- | 'compare' implemented by using 'compare' between all of the
-- components, lexicographically. First compares constructors.
gCompare
:: forall a. (ADT a, Constraints a Ord)
=> a -> a -> Ordering
gCompare x y = compare (ctorIndex x) (ctorIndex y)
<> mzipWith @Ord compare x y
{-# INLINE gCompare #-}
-- | '<=' implemented by using '<=' between all of the components. First
-- compares constructors.
gLTE
:: forall a. (ADT a, Constraints a Ord)
=> a -> a -> Bool
gLTE x y = not $ gGT x y
{-# INLINE gLTE #-}
-- | '<' implemented by using '<' between all of the components. First
-- compares constructors.
gLT :: forall a. (ADT a, Constraints a Ord)
=> a -> a -> Bool
gLT x y = ctorIndex x < ctorIndex y
|| getAny (mzipWith @Ord (\x' -> Any . (x' <)) x y)
{-# INLINE gLT #-}
-- | '>=' implemented by using '>=' between all of the components. First
-- compares constructors.
gGTE
:: forall a. (ADT a, Constraints a Ord)
=> a -> a -> Bool
gGTE x y = not $ gLT x y
{-# INLINE gGTE #-}
-- | '>' implemented by using '>' between all of the components. First
-- compares constructors.
gGT :: forall a. (ADT a, Constraints a Ord)
=> a -> a -> Bool
gGT x y = ctorIndex x > ctorIndex y
|| getAny (mzipWith @Ord (\x' -> Any . (x' >)) x y)
{-# INLINE gGT #-}
-- | 'max' implemented by using 'max' between all of the components. First
-- compares constructors. If two items are equal, returns the second.
gMax
:: forall a. (ADT a, Constraints a Ord)
=> a -> a -> a
gMax x y | gLTE x y = y
| otherwise = x
{-# INLINE gMax #-}
-- | 'min' implemented by using 'min' between all of the components. First
-- compares constructors. If two items are equal, returns the first.
gMin
:: forall a. (ADT a, Constraints a Ord)
=> a -> a -> a
gMin x y | gLTE x y = x
| otherwise = y
{-# INLINE gMin #-}