5
votes

I'm trying to write down the Category of (finite-dimensional free)vector spaces, but I can't seem to convince GHC that any given length indexed vector is Applicative.

here's what I have:

{-# LANGUAGE DataKinds, PolyKinds, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, GADTs, DeriveTraversable, StandaloneDeriving #-}

-- | Quick(slow) and dirty typesafe vectors
module Vector where
import Control.Category

Vectors are lists with a length parameter

data Natural = Z | S Natural
data Vec (n :: Natural) a where
  VNil :: Vec Z a
  VCons :: a -> Vec n a -> Vec (S n) a
deriving instance Functor (Vec n)
deriving instance Foldable (Vec n)
deriving instance Traversable (Vec n)

To get the category, we need matrix multiplication. The obvious implementation makes the indices come out a little backwards from what we normally want.

vmult :: Num a => Vec i (Vec k a) -> Vec j (Vec k a) -> Vec j (Vec i a)
--                    ^      ^           ^      ^           ^      ^
vmult _ VNil = VNil
vmult xs (VCons y ys) = VCons (dotProduct y <$> xs) $ vmult xs ys

dotProduct :: Num a => Vec n a -> Vec n a -> a
dotProduct VNil VNil = 0
dotProduct (VCons x xs) (VCons y ys) = x * y + dotProduct xs ys

EDIT

with @Probie's help, I've managed to solve the earlier problem, which is sufficient to define an instance for Semigroupoids

data KNat n where
  KZ :: KNat Z
  KS :: Finite n => KNat n -> KNat (S n)

class Finite (a :: Natural) where toFNat :: proxy a -> KNat a
instance Finite Z where toFNat _ = KZ
instance Finite n => Finite (S n) where toFNat _= KS (toFNat (Proxy :: Proxy n))

instance Finite n => Applicative (Vec n) where
  pure :: forall a. a -> Vec n a
  pure x = go (toFNat (Proxy :: Proxy n))
    where
      go :: forall (m :: Natural). KNat m -> Vec m a
      go KZ = VNil
      go (KS n) = VCons x $ go n

  (<*>) :: forall a b. Vec n (a -> b) -> Vec n a -> Vec n b
  nfs <*> nxs = go (toFNat (Proxy :: Proxy n)) nfs nxs
    where
      go :: forall (m :: Natural). KNat m -> Vec m (a -> b) -> Vec m a -> Vec m b
      go KZ VNil VNil = VNil
      go (KS n) (VCons f fs) (VCons x xs) = VCons (f x) (go n fs xs)

data Matrix a i j where
  Matrix :: (Finite i, Finite j) => Vec i (Vec j a) -> Matrix a i j

instance Num a => Semigroupoid (Matrix a) where
  Matrix x `o` Matrix y = Matrix (vmult (sequenceA x) y)

but I run into a similar problem as before when defining Category.id:

instance Num a => Category (Matrix a) where
  (.) = o
  id :: forall (n :: Natural). Matrix a n n
  id = Matrix (go (toFNat (Proxy :: Proxy n)))
    where
      go :: forall (m :: Natural). (KNat m) -> Vec m (Vec m a)
      go KZ = VNil
      go (KS n) = VCons (VCons 1 (pure 0)) (VCons 0 <$> go n)

Instead of needing to summon an Applicative (Vec n) from thin air, I now need a Finite n.

src/Vector.hs:59:8: error:
    • Could not deduce (Finite n) arising from a use of ‘Matrix’
      from the context: Num a
        bound by the instance declaration at src/Vector.hs:56:10-37
      Possible fix:
        add (Finite n) to the context of
          the type signature for:
            Control.Category.id :: forall (n :: Natural). Matrix a n n
    • In the expression: Matrix (go (toFNat (Proxy :: Proxy n)))
      In an equation for ‘Control.Category.id’:
          Control.Category.id
            = Matrix (go (toFNat (Proxy :: Proxy n)))
            where
                go :: forall (m :: Natural). (KNat m) -> Vec m (Vec m a)
                go KZ = VNil
                go (KS n) = VCons (VCons 1 (pure 0)) (VCons 0 <$> go n)
      In the instance declaration for ‘Category (Matrix a)’
   |
59 |   id = Matrix (go (toFNat (Proxy :: Proxy n)))
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

There seems to be no way out of needing a side condition for this.

end edits


For context, This is what I had before, Vec n is inductively Applicative

instance Applicative (Vec Z) where
  pure _ = VNil
  _ <*> _ = VNil
instance Applicative (Vec n) => Applicative (Vec (S n)) where
  pure a = VCons a $ pure a
  VCons f fs <*> VCons x xs = VCons (f x) (fs <*> xs)

to get a category instance, we need to rearrange the a behind the indices...

data Matrix a i j where
  Matrix :: Vec i (Vec j a) -> Matrix a i j

But there's no way to rearrange the indices themselves without rearranging one of the terms, too...

instance Num a => Category (Matrix a) where
  Matrix x . Matrix y = Matrix $ (vmult (sequenceA x) y)
--                                       ^^^^^^^^^

However:

src/Vector.hs:36:42: error:
    • Could not deduce (Applicative (Vec c))
        arising from a use of ‘sequenceA’
      from the context: Num a
        bound by the instance declaration at src/Vector.hs:35:10-37
    • In the first argument of ‘vmult’, namely ‘(sequenceA x)’
      In the second argument of ‘($)’, namely ‘(vmult (sequenceA x) y)’
      In the expression: Matrix $ (vmult (sequenceA x) y)
   |
36 |   Matrix x . Matrix y = Matrix $ (vmult (sequenceA x) y)
   |                                          ^^^^^^^^^^^
2
Haskell doesn't yet have dependent types. Have you tried building with a branch of GHC that does?user8174234
Have you seen the approach outlined here: gmalecha.github.io/reflections/2017/…Regis Kuckaertz

2 Answers

2
votes

I haven't played around too much with dependent types in Haskell, but this like something that should be possible. I've managed to get something which compiles, but I think there's a better way though...

The trick is to be able to produce something you can recurse over which carries sufficient type information without actually needing to already have a Vector. This allows us to collapse both Applicative instances into a single Applicative instance (unfortunately adding a constraint, Finite which hopefully won't cause an issue later)

{-# LANGUAGE DataKinds, PolyKinds, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, GADTs, DeriveTraversable, StandaloneDeriving, ScopedTypeVariables #-}

module Vector where
import Control.Category
import Data.Proxy

data Natural = Z | S Natural

data SNat n where
  SZ :: SNat Z
  SS :: Finite n => SNat n -> SNat (S n)

class Finite (a :: Natural) where
  toSNat :: proxy a -> SNat a

instance Finite Z where
  toSNat _ = SZ
instance (Finite a) => Finite (S a) where
  toSNat _ = SS (toSNat (Proxy :: Proxy a))

data Vec (n :: Natural) a where
  VNil :: Vec Z a
  VCons :: (Finite n) => a -> Vec n a -> Vec (S n) a

deriving instance Functor (Vec n)
deriving instance Foldable (Vec n)
deriving instance Traversable (Vec n)

instance (Finite n) => Applicative (Vec n) where
  pure (a :: a) = go (toSNat (Proxy :: Proxy n))
    where go :: forall (x :: Natural) . SNat x -> Vec x a
          go SZ = VNil
          go (SS m) = VCons a (go m)
  (fv :: Vec n (a -> b)) <*> (xv :: Vec n a) = go (toSNat (Proxy :: Proxy n)) fv xv
    where go :: forall (x :: Natural) . SNat x -> Vec x (a -> b) -> Vec x a -> Vec x b
          go SZ VNil VNil = VNil
          go (SS m) (VCons f fs) (VCons x xs) = VCons (f x) (go m fs xs)

vmult :: Num a => Vec i (Vec k a) -> Vec j (Vec k a) -> Vec j (Vec i a)
vmult _ VNil = VNil
vmult xs (VCons y ys) = VCons (dotProduct y <$> xs) $ vmult xs ys

dotProduct :: Num a => Vec n a -> Vec n a -> a
dotProduct VNil VNil = 0
dotProduct (VCons x xs) (VCons y ys) = x * y + dotProduct xs ys

data Matrix a i j where
  Matrix :: (Finite i, Finite j) => Vec i (Vec j a) -> Matrix a i j

instance Num a => Category (Matrix a) where
  Matrix x . Matrix y = Matrix $ (vmult (sequenceA x) y)

Edit: Matrix isn't a category. There is no identity - we require forall (n :: Natural) . Matrix a n n

Unfortunately, all kinds in Haskell are inhabited by Any, so we'd need to be able to have a Matrix a Any Any, but we can only have matrices where the dimensions are "true" Naturals, so the best we can ever do is forall (n :: Natural) . Finite n => Matrix a n n It turns out I'm wrong here and it can actually be done

1
votes

After a bit of proof by intimidation (and several hours on the train), I came up with this.

If you defer the proof of Finiteness to the very end, you can multiply anything... the terms we want to write are straitforward.

vdiag :: forall a i j. a -> a -> a -> KNat i -> KNat j -> Vec i (Vec j a)
vdiag u d l = go
  where
    go :: forall i' j'. KNat i' -> KNat j' -> Vec i' (Vec j' a)
    go (KS i) (KS j) =
      VCons (VCons d  $  vpure u j)
            (VCons l <$> go i j)
    go KZ _ = VNil
    go (KS i) KZ = vpure VNil (KS i)

vpure :: a -> KNat m -> Vec m a
vpure x KZ = VNil
vpure x (KS n) = VCons x $ vpure x n

But we don't know what i and j will be when we actually need to use them for Category.id (other than that they are equal). We CPS them! An extra constructor to Matrix a is given, with a Rank 2 constraint.

data Matrix a i j where
  DiagonalMatrix :: (Finite i => KNat i -> Vec i (Vec i a)) -> Matrix a i i
--                  ^^^^^^^^^                             ^
  Matrix :: (Finite i, Finite j) => Vec i (Vec j a) -> Matrix a i j

Any time We happen to need to multiply such things, we can use the fact that we know the inner index k from the other term being multiplied:

instance Num a => Semigroupoid (Matrix a) where
  o :: forall a i j k. Num a => Matrix a k j -> Matrix a i k -> Matrix a i j
  Matrix x          `o` Matrix y          =
      Matrix (vmult (sequenceA   x                             )   y)
  DiagonalMatrix fx `o` Matrix y          =
      Matrix (vmult (sequenceA (fx (toFNat (Proxy :: Proxy k))))   y)
  Matrix x          `o` DiagonalMatrix fy = 
      Matrix (vmult (sequenceA   x                             ) (fy (toFNat (Proxy :: Proxy k))))

that is, unless they're both diagonal. In any case, they're all the same, so we can use the index we get later for all three now:

  DiagonalMatrix fx `o` DiagonalMatrix fy = DiagonalMatrix $ 
      \i -> vmult (sequenceA (fx i)) (fy i)

It's because of this step that It's neccessary to restrict the CPS'ed Matrix to only square ones. At first I tried just CPSing all the time, but that requires the end user remember all of the intermediate indices and prove them, too, instead of only the indices of the result. Though I'm sure that could be made to work, at least for just a category, it's unnecessary and draconian.

Anyways, We're done now, id is a CPS'd vdiag.

instance Num a => Category (Matrix a) where
  (.) = o
  id = DiagonalMatrix $ \i -> vdiag 0 1 0 i i

of course, extracting rows and columns from a Matrix a requires that you know how big a matrix you've asked for is.

unMatrix :: (Finite i, Finite j) => Matrix a i j -> Vec i (Vec j a)
unMatrix (Matrix x) = x
unMatrix (DiagonalMatrix fx) = fx (toFNat (Proxy))

type Zero = Z
type One = S Z
type Two = S One
type Three = S Two
type Four = S Three

f :: Vec Two (Vec Three Int)
f = VCons (VCons 1 $ VCons 2 $ VCons 3 VNil)
  $ VCons (VCons 4 $ VCons 5 $ VCons 6 VNil)
  $ VNil

g :: Vec Four (Vec Two Int)
g = VCons (VCons 1 $ VCons 2 VNil)
  $ VCons (VCons 3 $ VCons 4 VNil)
  $ VCons (VCons 5 $ VCons 6 VNil)
  $ VCons (VCons 7 $ VCons 8 VNil)
  $ VNil

fg = unMatrix $ Matrix f . id . Matrix g
--                         ^^

but that doesn't seen to be too big an obligation.

> fg
VCons (VCons 9 (VCons 12 (VCons 15 VNil))) (VCons (VCons 19 (VCons 26 (VCons 33 VNil))) (VCons (VCons 29 (VCons 40 (VCons 51 VNil))) (VCons (VCons 39 (VCons 54 (VCons 69 VNil))) VNil)))

for completeness, here's the whole thing: https://gist.github.com/danbornside/f44907fe0afef17d5b1ce93dd36ce84d