This doesn't address the original question but one of the comments: addition of tensors. My understanding of tensors is as n-dimensional matrices. You already know that element-wise addition is just an application of zipWith
. But we can write the function addLists
using the applicative and functor typeclasses:
import Control.Applicative
addLists :: [Float] -> [Float] -> [Float]
addLists x y = (*) <$> x <*> y
or equivalently:
addLists :: [Float] -> [Float] -> [Float]
addLists = liftA2 (*)
Note that <$> == fmap
and <*>
is in the Applicative
class.
First we define a type for rank-0 tensors:
newtype Identity a = Identity {getIdentity :: a}
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
instance Applicative Identity where
pure a = Identity a
(<*>) (Identity f) (Identity a) = Identity (f a)
Then a type for adding a new layer on top of a rank-n tensor, creating a rank n+1 tensor:
newtype Compose f g a = Compose {getCompose :: f (g a)}
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose a) = Compose (fmap (fmap f) a)
instance (Applicative f, Applicative g) => Applicative (Compose f g) where
pure a = Compose (pure (pure a))
(<*>) (Compose f) (Compose a) = Compose (liftA2 (<*>) f a)
But wait, there's more! These type and their instances are already defined for you, namely in Data.Functor.Identity
and Data.Functor.Compose
. You can now write generic tensor element-wise addition:
addTensor :: Applicative f => f Float -> f Float -> f Float
addTensor = liftA2 (*)
The function liftA2
just generalizes zipWith
to Applicative
s. For rank-1 you have:
addTensor1 :: Compose [] Identity Float -> Compose [] Identity Float -> Compose [] Identity Float
addTensor1 = addTensor
This type is a bit noisy. You can easily define type synonyms:
type Tensor0 = Identity
type Tensor1 = Compose [] Tensor0
type Tensor2 = Compose [] Tensor1
type Tensor3 = Compose [] Tensor2
type Tensor4 = Compose [] Tensor3
Then:
addTensor3 :: Tensor3 Float -> Tensor3 Float -> Tensor3 Float
addTensor3 = addTensor
addTensor4 :: Tensor4 Float -> Tensor4 Float -> Tensor4 Float
addTensor4 = addTensor
Since addTensor
abstracts over all Applicative
s you may want to define a subclass of Applicative
which form valid tensors:
class Applicative t => Tensor t
instance Tensor Identity
instance Tensor t => Tensor (Compose [] t)
addTensor :: Tensor f => f Float -> f Float -> f Float
addTensor = liftA2 (*)
I suspect you may have other operations on tensors other than addition. This general form makes it very easy to define a large range of operations. But hypothetically if you only wanted addTensor
you could do something like:
class Tensor a where
addTensor :: a -> a -> a
instance Tensor Float where
addTensor = (+)
instance Tensor t => Tensor [t] where
addTensor = zipWith addTensor
Much simpler types result: addTensor :: [[[[Float]]]] -> [[[[Float]]]] -> [[[[Float]]]]
for rank-4.