Reputation: 8313
I have a parameterised type that I'd like to constrain to a numeric type, more specifically a Fractional
, e.g.:
data Rating a = (Fractional a) => Score a | Unscored deriving (Show, Eq)
so that the user of the API can define which non-integer type they might utilise (Float
or Double
?), but the internal API code I write can still perform arithmetic operations on a numeric type. I do not want it to be an integer because results of my "internal operations" may not be integers and my understanding is that using Fractional
would lead to more accurate results.
Compiling the above (in GHCI at least) gives me the following error:
Data constructor `Score' has existential type variables, a context, or a specialised result type
Score :: forall a. Fractional a => a -> Rating a
(Use ExistentialQuantification or GADTs to allow this)
In the definition of data constructor `Score'
In the data declaration for `Rating'
which suggests to me I'm doing something that I probably don't want to keep trying; i.e. my design is rubbish.
I guess I'm trying to say the following in this API: "when you use a Rating type, its parameter must be a subclass of Fractional
so I can perform accurate arithmetic on it". How might I achieve this? Or am I way off the mark and/or overengineering?
Upvotes: 2
Views: 326
Reputation: 3316
As the error says, you can do this with GADTs
:
{-# LANGUAGE GADTs, StandaloneDeriving #-}
data Rating a where
Score :: (Fractional a) => a -> Rating a
Unscored :: Rating a
deriving instance (Show a) => Show (Rating a)
deriving instance (Eq a) => Eq (Rating a)
-- No need for a context here!
halfScore :: Rating a -> Rating a
halfScore (Score a) = Score (a / 2)
halfScore Unscored = Unscored
StandaloneDeriving
is necessary because GHC can't derive Show
and Eq
for GADTs otherwise.
Upvotes: 2
Reputation: 2602
You shouldn't put the Fractional
constraint on the datatype, but rather on the functions that work with it. So
data Rating a = Score a | Unscored deriving (Show, Eq)
makeSomeRating :: Fractional a => a -> Rating a
makeSomeRating x = Score (x / 2) -- can use Fractional functions here
doSomethingElseWithRating :: Fractional a => Rating a -> Something
Upvotes: 6