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.