ThinkRedstone
ThinkRedstone

Reputation: 133

Creating a typeclass that returns another instance of that typeclass in haskell

I'm trying to create a a system to derive symbolic functions, and I have a problem:

I have a typeclass for expressions, Exp, that defines a derivative function:

class Exp e where 
    derivative :: (Exp d) => e -> d

I want that class to have a few instances:

data Operator a b = a :* b | a :+ b

instance (Exp a, Exp b) => Exp (Operator a b)  where
    derivative (f :* g) = ((derivative f) :* g) :+ (f :* (derivative g)) --The derivative of the multiplication of two expressions 
    derivative (f :+ g) = derivative f :+ derivative g --The derivative of the addition of two expressions

instance Exp Double where
    derivative a = (0 :: Double)  --The derivative of a constant value is 0


instance Exp Char where
    derivative c = (1 :: Double) --The derivative of just a variable is one

what I get I compile with ghci is:

math.hs:19:21: error:
• Couldn't match expected type ‘d’ with actual type ‘Double’
  ‘d’ is a rigid type variable bound by
    the type signature for:
      derivative :: forall d. Exp d => Double -> d
    at math.hs:19:5
• In the expression: (0 :: Double)
  In an equation for ‘derivative’: derivative a = (0 :: Double)
  In the instance declaration for ‘Exp Double’
• Relevant bindings include
    derivative :: Double -> d (bound at math.hs:19:5)

math.hs:22:21: error:
• Couldn't match expected type ‘d’ with actual type ‘Double’
  ‘d’ is a rigid type variable bound by
    the type signature for:
      derivative :: forall d. Exp d => Char -> d
    at math.hs:22:5
• In the expression: (1 :: Double)
  In an equation for ‘derivative’: derivative c = (1 :: Double)
  In the instance declaration for ‘Exp Char’
• Relevant bindings include
    derivative :: Char -> d (bound at math.hs:22:5)

math.hs:28:27: error:
• Couldn't match expected type ‘d’
              with actual type ‘Operator (Operator a0 b) (Operator a b0)’
  ‘d’ is a rigid type variable bound by
    the type signature for:
      derivative :: forall d. Exp d => Operator a b -> d
    at math.hs:27:5
• In the expression: derivative f :+ derivative g
  In an equation for ‘derivative’:
      derivative (f :+ g) = derivative f :+ derivative g
  In the instance declaration for ‘Exp (Operator a b)’
• Relevant bindings include
    g :: b (bound at math.hs:28:22)
    f :: a (bound at math.hs:28:17)
    derivative :: Operator a b -> d (bound at math.hs:27:5)

My question is this: why are my instance decelerations problematic? The derivative in each always resolves into an instance of Exp that is required by the type constrain of derivative, so why can't it match the type?

Upvotes: 2

Views: 112

Answers (2)

lehins
lehins

Reputation: 9767

There are ways to achieve the desired behavior, one is using FunctionalDependencies and MultiParamTypeClasses, while the other one is using TypeFamilies, which is showed below:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
module Main where

class Exp e where
    type ResExp e :: * -- type family of resulting expression
    derivative :: (Exp (ResExp e)) => e -> ResExp e

instance Exp Double where
    type ResExp Double = Double
    derivative a = 0


instance Exp Char where
    type ResExp Char = Double
    derivative c = 1

But when it comes to the instance for Operator, there are two bugs in implementation:

  1. Attempt to construct an infinite type
  2. derivative (f :* g) and derivative (f :+ g) have different return types.

Here is a way how to solve this problem though:

data Mult a b = a :* b

data Plus a b = a :+ b

instance (Exp a, Exp b, Exp (ResExp a), Exp (ResExp b)) => Exp (Plus a b) where
    type ResExp (Plus a b) = (Plus (ResExp a) (ResExp b))
    derivative (f :+ g) = derivative f :+ derivative g


instance (Exp a, Exp b, Exp (ResExp a), Exp (ResExp b)) => Exp (Mult a b) where
    type ResExp (Mult a b) = Plus (Mult (ResExp a) b) (Mult a (ResExp b))
    derivative (f :* g) = ((derivative f) :* g) :+ (f :* (derivative g))

Upvotes: 1

yeputons
yeputons

Reputation: 9238

When you write

class Exp e where 
    derivative :: (Exp d) => e -> d

you declare that any type e which is in the Exp class should have a function derivative :: e -> d. Note that e here is a very specific class, but d is only said to be in Exp. It's almost an arbitrary type. So you're trying to define a function which, given an argument, returns a value of arbitrary type, belonging to Exp

Selection of d is left to the compiler depending on context, like with fromInteger. So you're not saying "for each e there is a d belonging to Exp such that derivative will return d", you say that "for each e all d belonging to Exp are such that derivative will return d". If you want to say the former, you will probably have to use multi-parameterized classes and functional dependencies (to specify that type of the output is uniquely determined by type of the input).

If we replace e with some specific type, you're trying to implement the following:

derivative :: Exp d => Double -> d
derivative = (0::Double)

Which is not something you can do, because not all Exps are doubles. Say, Operator Double Double (which is in Exp) is clearly not a Double. Even more artificial example with the same problem:

derivative :: Double -> a
derivative = (0::Double)

Upvotes: 5

Related Questions