Can I constrain a type family?

前端 未结 4 1311
灰色年华
灰色年华 2020-12-12 19:09

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

4条回答
  •  醉话见心
    2020-12-12 19:58

    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.

    Tiny Dict

    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
    

    Infinitely differentiable types

    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
    

    Capturing the dictonary

    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
    

    Recovering the dictionary

    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
    

提交回复
热议问题