In this recent answer of mine, I happened to crack open this old chestnut (a program so old, half of it was written in the seventeenth century by Leibniz and written on a co
This can be accomplished in the same manner as Edward suggests with a tiny implementation of Dict. First, let's get the language extensions and imports out of the way.
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
import Data.Proxy
TypeOperators is only for your example problem.
We can make our own tiny implementation of Dict. Dict uses a GADT and ConstraintKinds to capture any constraint in the constructor for a GADT.
data Dict c where
Dict :: c => Dict c
withDict and withDict2 reintroduce the constraint by pattern matching on the GADT. We only need to be able to reason about terms with one or two sources of constraints.
withDict :: Dict c -> (c => x) -> x
withDict Dict x = x
withDict2 :: Dict a -> Dict b -> ((a, b) => x) -> x
withDict2 Dict Dict x = x
Now we can talk about infinitely differentiable types, whose derivatives must also be differentiable
class Differentiable f where
type D f :: * -> *
d2 :: p f -> Dict (Differentiable (D f))
-- This is just something to recover from the dictionary
make :: a -> f a
d2 takes a proxy for the type, and recovers the dictionary for taking the second derivative. The proxy allows us to easily specify which type's d2 we are talking about. We can get to deeper dictionaries by applying d:
d :: Dict (Differentiable t) -> Dict (Differentiable (D t))
d d1 = withDict d1 (d2 (pt (d1)))
where
pt :: Dict (Differentiable t) -> Proxy t
pt = const Proxy
The polynomial functor types, products, sums, constants, and zero, are all infinitely differentiable. We will define the d2 witnesses for each of these types
data K x = K deriving (Show)
newtype I x = I x deriving (Show)
data (f :+: g) x = L (f x)
| R (g x)
deriving (Show)
data (f :*: g) x = f x :&: g x deriving (Show)
Zero and constants don't require any additional knowledge to capture their derivative's Dict
instance Differentiable K where
type D K = K
make = const K
d2 = const Dict
instance Differentiable I where
type D I = K
make = I
d2 = const Dict
Sum and product both require the dictionaries from both of their component's derivatives to be able to deduce the dictionary for their derivative.
instance (Differentiable f, Differentiable g) => Differentiable (f :+: g) where
type D (f :+: g) = D f :+: D g
make = R . make
d2 p = withDict2 df dg $ Dict
where
df = d2 . pf $ p
dg = d2 . pg $ p
pf :: p (f :+: g) -> Proxy f
pf = const Proxy
pg :: p (f :+: g) -> Proxy g
pg = const Proxy
instance (Differentiable f, Differentiable g) => Differentiable (f :*: g) where
type D (f :*: g) = (D f :*: g) :+: (f :*: D g)
make x = make x :&: make x
d2 p = withDict2 df dg $ Dict
where
df = d2 . pf $ p
dg = d2 . pg $ p
pf :: p (f :*: g) -> Proxy f
pf = const Proxy
pg :: p (f :*: g) -> Proxy g
pg = const Proxy
We can recover the dictionary for constraints that we otherwise wouldn't have adequate information to deduce. Differentiable f would normally only let use get to make :: a -> f a, but not to either make :: a -> D f a or make :: a -> D (D f) a.
make1 :: Differentiable f => p f -> a -> D f a
make1 p = withDict (d2 p) make
make2 :: Differentiable f => p f -> a -> D (D f) a
make2 p = withDict (d (d2 p)) make