Declaring a "subclass" in Haskell
I am having problems with the following simple code in Haskell:
import Prelude hiding (cycle).
class ICycle a where
cycle :: a -> a
instance ICycle [a] where
cycle [] = []
cycle (x:xs) = xs ++ [x]
instance ICycle Bool where
cycle True = False
cycle False = True
instance Num a => ICycle a where
cycle n = n+1
main = do
print $ cycle $ [1,2,3]
print $ cycle $ True
print $ cycle $ 42
Here the first two instance declarations work as expected, but the third throws different kinds of errors depending on the flag combinations.
I know it is Num a
not shorter ICycle a
and hence the compiler cannot complete the type check. I've seen in the examples that this is bypassed either by the right side having a larger term, or by declaring the class of interests as a subclass of other classes. Here, on the contrary, I essentially want to declare the existing class as a subclass of the new one.
I wonder if there is any objection to this kind of use of typeclasses. Or if there is a natural solution.
source to share
For this specific example, I think you are better off using newtype
to wrap the instance:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Prelude hiding (cycle)
class ICycle a where
cycle :: a -> a
newtype Succ a = Succ { runSucc :: a }
deriving (Num, Eq, Ord, Bounded, Enum, Show, Read)
newtype Pred a = Pred { runPred :: a }
deriving (Num, Eq, Ord, Bounded, Enum, Show, Read)
instance Enum a => ICycle (Succ a) where
cycle = Succ . succ . runSucc
instance Enum a => ICycle (Pred a) where
cycle = Pred . pred . runPred
main = do
print $ cycle $ (42 :: Succ Int)
print $ cycle $ (42 :: Pred Int)
There are several ways that one could cycle through the numbers - by succ, by pred, by doubling, twice. The advantage of using newtype
for instance (which makes RHS "bigger" as you noted in your question) is that it allows us to have ALL of them.
The standard library does the same trick with Product
and Sum
for Monoid
.
Look at it in a different way, if it was possible to define a new superclass for Num
by adding a default implementation for all instances
Num
, then you would take that choice away from all of those implementations. It might not make sense.
source to share
According to the Haskell 2010 report, Chapter 4, Declarations and Bindings , the subject to be defined as an instance must be a constructor type. Thus,
instance Num a => ICycle a where
...
is invalid because it a
is a type variable and not a type constructor.
So the valid way to do this is unfortunately by type. Need to say:
instance ICycle Int where
...
instance ICycle Double where
...
etc.
source to share