2

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)

Wilco Verhoef
  • 90
  • 1
  • 9
  • 1
    See also [Lambda for type expressions in Haskell?](https://stackoverflow.com/q/4069840/791604). The type-level language of base Haskell is really impoverished. This is on purpose, to make implementing it easier. – Daniel Wagner Oct 18 '21 at 23:28

1 Answers1

8

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.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • Thank you! TypeFamilies solves this problem beautifully. I ended up scrapping the `Fractional` constraint, in favor of `Integral`, by having the `Domain` be a Ratio. (I'll append it to my original post for clarity) – Wilco Verhoef Oct 19 '21 at 11:44