{-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -Wall #-} ---------------------------------------------------------------------- -- | -- Module : Data.AddBounds -- Copyright : (c) Conal Elliott 2008 -- License : GNU AGPLv3 (see COPYING) -- -- Maintainer : conal@conal.net -- Stability : experimental -- -- Add bounds to an ordered type ---------------------------------------------------------------------- module Data.AddBounds (AddBounds(..)) where import Control.Applicative (pure,(<$>)) import Data.Unamb (unamb) import Data.AffineSpace -- Testing import Test.QuickCheck import Test.QuickCheck.Checkers -- | Wrap a type into one having new least and greatest elements, -- preserving the existing ordering. data AddBounds a = MinBound | NoBound a | MaxBound deriving (Eq {-, Ord-}, Read, Show) instance Bounded (AddBounds a) where minBound = MinBound maxBound = MaxBound -- Normally, I'd derive 'Ord' as well, but there's a sticky point. The -- derived instance uses the default definition of 'min', which is uses -- '(<=)' and thus cannot exploit any partial information. So, define our -- own 'min' in terms of 'min' on @a@. -- Examples: -- (NoBound undefined) `min` (NoBound undefined) can return (NoBound _|_) -- using this definition, but will not produce any output using the -- default min. -- -- (NoBound a) `min` (NoBound b) can return partial information from -- a `min` b while the default implementation cannot. -- instance Ord a => Ord (AddBounds a) where -- MinBound <= _ = True -- NoBound _ <= MinBound = False -- NoBound a <= NoBound b = a <= b -- NoBound _ <= MaxBound = True -- MaxBound <= MaxBound = True -- MaxBound <= _ = False -- given previous -- MinBound `min` _ = MinBound -- _ `min` MinBound = MinBound -- NoBound a `min` NoBound b = NoBound (a `min` b) -- u `min` MaxBound = u -- MaxBound `min` v = v -- MinBound `max` v = v -- u `max` MinBound = u -- NoBound a `max` NoBound b = NoBound (a `max` b) -- _ `max` MaxBound = MaxBound -- MaxBound `max` _ = MaxBound -- The definition above is too strict for some uses. Here's a parallel -- version. -- Alternatively, make a non-parallel definition here and use 'pmin' -- instead of 'min' where I want. -- General recipe for Ord methods: use unamb to try two strategies. The -- first one, "justB", only examines b. The second one first examines -- only examines a and then examines both. I take care that the two -- strategies handle disjoint inputs. I could instead let the second -- strategy handle the first one redundantly, being careful that they -- agree. -- This instance is very like the one Richard Smith (lilac) constructed. -- It fixes a couple of small bugs and follows a style that helps me see -- that I'm covering all of the cases with the evaluation order I want. instance Ord a => Ord (AddBounds a) where a <= b = justB b `unamb` (a <=* b) where justB MaxBound = True justB _ = undefined MinBound <=* _ = True _ <=* MinBound = False NoBound u <=* NoBound v = u <= v MaxBound <=* NoBound _ = False _ <=* MaxBound = undefined a `min` b = justB b `unamb` (a `min'` b) where justB MinBound = MinBound justB MaxBound = a justB (NoBound _) = undefined MinBound `min'` _ = MinBound MaxBound `min'` v = v NoBound u `min'` NoBound v = NoBound (u `min` v) _ `min'` MinBound = undefined _ `min'` MaxBound = undefined a `max` b = justB b `unamb` (a `max'` b) where justB MaxBound = MaxBound justB MinBound = a justB (NoBound _) = undefined MaxBound `max'` _ = MaxBound MinBound `max'` v = v NoBound u `max'` NoBound v = NoBound (u `max` v) _ `max'` MaxBound = undefined _ `max'` MinBound = undefined -- instance Arbitrary a => Arbitrary (AddBounds a) where -- arbitrary = frequency [ (1 ,pure MinBound) -- , (10, NoBound <$> arbitrary) -- , (1 ,pure MaxBound) ] -- coarbitrary MinBound = variant 0 -- coarbitrary (NoBound a) = variant 1 . coarbitrary a -- coarbitrary MaxBound = variant 2 instance Arbitrary a => Arbitrary (AddBounds a) where arbitrary = frequency [ (1 ,pure MinBound) , (10, NoBound <$> arbitrary) , (1 ,pure MaxBound) ] instance CoArbitrary a => CoArbitrary (AddBounds a) where coarbitrary MinBound = variant (0::Int) coarbitrary (NoBound a) = variant (1::Int) . coarbitrary a coarbitrary MaxBound = variant (2::Int) instance (EqProp a, Eq a) => EqProp (AddBounds a) where NoBound a =-= NoBound b = a =-= b u =-= v = u `eq` v -- Hm. I'm dissatisfied with this next instance. I'd like to tweak my -- type definitions to eliminate these partial definitions. instance AffineSpace t => AffineSpace (AddBounds t) where type Diff (AddBounds t) = Diff t NoBound u .-. NoBound v = u .-. v -- I don't know what to do here _ .-. _ = error "(.-.) on AddBounds: only defined on NoBound args" NoBound u .+^ v = NoBound (u .+^ v) _ .+^ _ = error "(.+^) on AddBounds: only defined on NoBound args"