简体   繁体   中英

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

I'm trying to create aa 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?

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)

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))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM