As @leftroundabout has commented, you don't need a constraint here. Don't be fooled by the word "constraint". In Haskell, the primary purpose of constraints isn't to constrain the behavior of a particular type or operation in some manner. Rather, it's to constrain the set of types that a function will accept to those types that support a set of operations.
When I write:
fmapTwice :: (Functor f) => (a -> a) -> f a -> f a
fmapTwice f = fmap (f . f)
I'm not really constraining the type f
to act like a functor and obey the rules required of functors. Rather, I'm constraining the fmapTwice
function to only apply to types f
that support the fmap
operation.
Nothing stops some jerk from writing:
data Foo a = Foo a | NoFoo deriving (Show)
instance Functor Foo where
fmap _ _ = NoFoo -- invalid functor violates: fmap id = id
and applying my function to this invalid functor:
> fmapTwice (*2) (Foo 10)
NoFoo
>
Haskell relies on programmer discipline to ensure that something declared as having a Functor
instance is a well behaved functor.
In your example, the instance:
import Data.Semigroup
import qualified Data.Map as Map
import Data.Map.Strict (Map)
data SemigroupPolynomial a = SP (Map a Integer) deriving (Show)
instance (Ord a) => Semigroup (SemigroupPolynomial a) where
(SP p0) <> (SP p1) =
SP $ Map.filter (0/=) $ Map.unionWith (+) p0 p1
doesn't require any constraints other than Ord a
, to ensure that a
can be used as a Map
key.
Now, it's up to you to make sure you only use your SemigroupPolynomial
to represent commutative operations:
foldSP :: (a -> a -> a) -> SemigroupPolynomial a -> a
foldSP f (SP m) = foldr1 f $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
main = do let sp = singleton 3 <> singleton 3 <> singleton 6
print sp
print $ foldSP (*) sp
print $ foldSP (+) sp
print $ foldSP (-) sp -- wrong, but it's your own damn fault
If you want to somehow introduce a requirement of commutativity into your data type, one way of doing it (that doesn't involve Haskell "constraints" at all) is to write something like:
data CommutativeOp a = CO (a -> a -> a)
foldSP :: CommutativeOp a -> SemigroupPolynomial a -> a
foldSP (CO f) (SP m) = <same as above>
Now, as long as you realize that when you write:
plusOp = CO (+)
timesOp = CO (*)
you are making a declaration that (+)
and (*)
are commutative operations, this will ensure that foldSP
is only applied to such operations:
main = do let sp = singleton 3 <> singleton 3 <> singleton 6
print $ foldSP plusOp sp
print $ foldSP timesOp sp
If you want to somehow introduce a commutativity constraint on the type a
to ensure that SemigroupPolynomial a
is a valid representation, then you can't do this for a
equal to Int
, obviously, since it depends on which binary operation Int -> Int -> Int
is used for the fold.
Instead, you need to embed the operation into the type, perhaps using newtype
s that represent the operation, like Sum
or Product
in Data.Semigroup
. Then, you can introduce a type class (with no operations) to represent the commutativity constraint:
class Commutative a
instance Commutative (Sum a)
instance Commutative (Product a)
instance (Ord a, Commutative b) => SemigroupPolynomial b where
...definition on (<>) as above...
and now the fold operation would use the operation implicit in the newtype (here, just using the monoid instance):
foldSP' :: (Monoid a) => SemigroupPolynomial a -> a
foldSP' (SP m) = mconcat $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
Maybe this is what you wanted. If so, the full example looks like this:
import Data.Semigroup
import qualified Data.Map as Map
import Data.Map.Strict (Map)
newtype SemigroupPolynomial a = SP (Map a Integer) deriving (Show)
class Commutative a
instance Commutative (Sum a)
instance Commutative (Product a)
instance (Ord a, Commutative a) => Semigroup (SemigroupPolynomial a) where
(SP p0) <> (SP p1) =
SP $ Map.filter (0/=) $ Map.unionWith (+) p0 p1
singleton :: a -> SemigroupPolynomial a
singleton x = SP $ Map.singleton x 1
foldSP' :: (Monoid a) => SemigroupPolynomial a -> a
foldSP' (SP m) = mconcat $ concatMap (\(a, n) -> replicate (fromIntegral n) a)
(Map.assocs m)
main = do let sp1 = singleton (Sum 3) <> singleton (Sum 3) <> singleton (Sum 6)
print sp1
print (foldSP' sp1)
let sp2 = singleton (Product 3) <> singleton (Product 3)
<> singleton (Product 6)
print sp2
print (foldSP' sp2)