Reputation: 133
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
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:
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
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 Exp
s 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