Home > Back-end >  How do I make a composed data constructor an instance of a class?
How do I make a composed data constructor an instance of a class?

Time:10-20

I have declared the following datatype

data Poly a = Poly [a]

and also a class:

class Polynom p where
  substitute :: p a -> a -> a
  compose :: p a -> p a -> p a

I can make the datatype an instance of the class:

instance Polynom Poly where
  substitute = ...
  compose = ...

I also want a Ratio of a Poly to be an instance of the same class; I have tried many syntaxes and language extensions, but none of them work; things like:

instance Polynom (Ratio . Poly) where ...
-- error: cannot use (.) there

instance Poly p => Polynom (Ratio (p _)) where ...
-- error: no wildcards allowed

type X a = Ratio (Poly x)
instance Polynom X where ...
-- error: X needs another param

instance Polynom (* -> Ratio (Poly *)) where ...
-- error: wrong kind

My goal is to be able to have substitute and compose work on a Ratio (Poly *); for example:

rf1 = Poly [1,2,3] % Poly [4,5,6] :: Ratio (Poly Int)
rf2 = Poly [0,1] % Poly [1,0] :: Ratio (Poly Int)
rf = rf1 `compose` rf2 :: Ratio (Poly Int)
result = substitute rf 10 :: Int

Is this even possible in Haskell? If so, what syntax or language extension am I missing here?

Update

I solved this using TypeFamilies; as @leftaroundabout suggested. The working class instance looks like this*:

instance Integral a => Polynom (Ratio (Poly a)) where
  type Domain (Ratio (Poly a)) = Ratio a
  substitute (n :% d) x = substitute ((%1) <$> n) x
                        / substitute ((%1) <$> d) x
  compose (n :% d) = substitute ((pure <$> n) % (pure <$> d))

*(I actually also changed the bad names; but kept them here to avoid confusion)

CodePudding user response:

First off... if you want that instance then the class is named wrong; the quotient of two polynomials is not a polynomial.

Second off, you can't even have instances for actual polynomials as the class stands, you do need at least Num constraints.

class Polynom p where
  substitute :: Num a => p a -> a -> a
  compose :: Num a => p a -> p a -> p a

That said, as far as Haskell is concerned then there isn't too much in your way here.

. obviously can't be used one the type level, but there's Compose which is kind of the equivalent thing.

data Compose f g a = Compose { getCompose :: f (g a) }

That is meant to be used for composing functors, which Ratio is not, however I see no reason not to just use it to compose arbitrary Type -> Type things.

instance Polynom (Compose Ratio Poly) where
  substitute (r%d) = substitute r / substitute d

...at this point you'll find that the Num constraint wasn't enough. Requiring Fractional in the Polynomial class would be kind of silly though.

I would suggest you approach it entirely different. You could use a class like

{-# LANGUAGE TypeFamilies #-}

import Data.Kind (Type)

class Endofunction f where
  type Domain f :: Type
  evaluate :: f -> Domain f -> Domain f
  compose :: f -> f -> f

Then the instances are

instance Num a => Endofunction (Poly a) where
  type Domain (Poly a) = a
  ...

and, now not needing Compose,

instance Fractional a => Endofunction (Ratio (Poly a)) where
  type Domain (Ratio (Poly a)) = a
  ...

I suspect you could actually see it as a functor between some two categories, but it's certainly not a Hask endofunctor.

  • Related