Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe and polymorphic Enum in Haskell

I'm trying to write total versions of the unsafe Enum functions:

predMay :: Enum a => a -> Maybe a
succMay :: Enum a => a -> Maybe a
toEnumMay :: Enum a => Int -> Maybe a

My problem is that the unsafe functions are only partial if the Enum is also Bounded, so safe versions should have a different form in the two cases:

predMayUnboundedEnum :: Enum a => a -> Maybe a
predMayUnboundedEnum = Just . pred

predMayBoundedEnum :: (Enum a, Bounded a) => a -> Maybe a
predMayBoundedEnum x
  | fromEnum x == fromEnum (minBound `asTypeOf` x) = Nothing
  | otherwise  = Just (pred x)

The best idea I've had to get the function I want is to use another typeclass:

class Enum a => SafeEnum a where
  predMay :: a -> Maybe a
instance Enum a => SafeEnum a where
  predMay = predMayUnboundedEnum
instance (Enum a, Bounded a) => SafeEnum a where
  predMay = predMayBoundedEnum

but this causes a complaint about Duplicate instance declarations.

Do I have the right idea here, or is there a better way to approach this problem? Has someone else already done this? (I'm aware of the package prelude-safeenum, but the primary advantage of Enum for me is that we can deriving it.) Are these safe functions even possible, or are Enums in the wild too hairy to let such a simple solution be safe?

like image 453
gilgamec Avatar asked Nov 11 '16 13:11

gilgamec


1 Answers

This cannot be done. Remember that Haskell type classes are open. Your proposal requires to do something different depending on whether or not a type belongs to the Bounded class. But you can never know a type doesn't belong to this class. (Well, you can know that e.g. Integer must not have a Bounded instance, but the compiler can not. Nothing stops you from defining a nonsense instance Bounded Integer where {minBound = 7; maxBound = 3} except common sense.)

The only reliable way to get the correct instances is to sort types manually in bounded-or-not. This can be done consisely enough, especially with a default:

class Enum a => SafeEnum a where
  predMay :: a -> Maybe a
  predMay = predMayUnboundedEnum

instance SafeEnum Integer
instance SafeEnum Rational
instance SafeEnum Int where predMay = predMayBoundedEnum
...
like image 127
leftaroundabout Avatar answered Sep 28 '22 18:09

leftaroundabout