content: {-# OPTIONS_HADDOCK show-extensions #-}
  
  {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE KindSignatures #-}
  {-# LANGUAGE DeriveFunctor #-}
  {-# LANGUAGE DeriveFoldable #-}
  {-# LANGUAGE DeriveTraversable #-}
  {-# LANGUAGE TypeOperators #-}
  {-# LANGUAGE TypeFamilies #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  
  {-|
  Module      : Data.FixedSize.Matrix
  Description : fixed-size matrices
  Copyright   : (c) Lars Brünjes, 2016
  License     : MIT
  Maintainer  : brunjlar@gmail.com
  Stability   : experimental
  Portability : portable
  
  This module defines fixed-size /matrices/ and some basic typeclass instances and operations for them.
  -}
  
  module Data.FixedSize.Matrix
      ( Matrix(..)
      , (<%%>)
      , row
      , column
      , transpose
      ) where
  
  import Data.MyPrelude
  import Data.FixedSize.Class
  import Data.FixedSize.Vector
  import GHC.TypeLits
  
  -- | @'Matrix' m n a@ is the type of /matrices/ with @m@ rows, @n@ columns and entries of type @a@.
  --
  newtype Matrix (m :: Nat) (n :: Nat) a = Matrix (Vector m (Vector n a))
      deriving (Eq, Show, Functor, Foldable, Traversable, NFData)
  
  instance (KnownNat m, KnownNat n) => Applicative (Matrix m n) where
  
      pure x = Matrix $ pure (pure x)
  
      Matrix fs <*> Matrix xs = Matrix $ (<*>) <$> fs <*> xs
  
  instance (KnownNat m, KnownNat n) => FixedSize (Matrix m n) where
  
      type Index (Matrix m n) = (Int, Int)
  
      type Size (Matrix m n) = m * n
  
      m !? (i, j) = row m i >>= (!? j)
  
      generate f = Matrix $ generate (\i -> generate (\j -> f (i, j)))
  
  -- | Multiplication of a /matrix/ by a (column-)/vector/.
  --
  -- >>> :set -XDataKinds
  -- >>> (pure 1 :: Matrix 1 2 Int) <%%> cons 1 (cons 2 nil)
  -- [3]
  --
  (<%%>) :: Num a => Matrix m n a -> Vector n a -> Vector m a
  Matrix rows <%%> v = (v <%>) <$> rows
  
  -- | Gives the matrix row with the specified index (starting at zero) if the index is valid,
  --   otherwise 'Nothing'.
  --
  -- >>> :set -XDataKinds
  -- >>> row (pure 42 :: Matrix 2 4 Int) 0
  -- Just [42,42,42,42]
  --
  -- >>> row (pure 42 :: Matrix 2 4 Int) 2
  -- Nothing
  --
  row :: KnownNat m => Matrix m n a -> Int -> Maybe (Vector n a)
  row (Matrix rows) = (rows !?)
  
  -- | Gives the matrix column with the specified index (starting at zero) if the index is valid,
  --   otherwise 'Nothing'.
  --
  -- >>> :set -XDataKinds
  -- >>> column (pure 42 :: Matrix 2 4 Int) 3
  -- Just [42,42]
  --
  -- >>> column (pure 42 :: Matrix 2 4 Int) 4
  -- Nothing
  --
  column :: KnownNat n => Matrix m n a -> Int -> Maybe (Vector m a)
  column (Matrix rows) j = sequenceA $ (!? j) <$> rows
  
  -- | Transposes a matrix.
  --
  -- >>> transpose (Matrix $ cons (cons 'a' nil) (cons (cons 'b' nil) nil))
  -- Matrix ["ab"]
  --
  transpose :: (KnownNat m, KnownNat n) => Matrix m n a -> Matrix n m a
  transpose m = generate $ \(i, j) -> fromJust $ m !? (j, i)
  