Alex
Alex

Reputation: 8313

Can I constrain the parametric polymorphic type on type/data constructor in Haskell?

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

Answers (2)

xnyhps
xnyhps

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

Twan van Laarhoven
Twan van Laarhoven

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

Related Questions