diff --git a/numhask-array.cabal b/numhask-array.cabal
--- a/numhask-array.cabal
+++ b/numhask-array.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name:          numhask-array
-version:       0.8.0
+version:       0.9.0
 synopsis:
     Multi-dimensional array interface for numhask.
 description:
@@ -8,7 +8,6 @@
     .
     == Usage
     .
-    >>> {-# LANGUAGE NegativeLiterals #-}
     >>> {-# LANGUAGE RebindableSyntax #-}
     >>> import NumHask.Prelude
     >>> import NumHask.Array
@@ -36,12 +35,11 @@
 license-file:
   LICENSE
 tested-with:
-  GHC==8.10.1
+  GHC==8.10.1 GHC==9.0.1
 build-type:
   Simple
 extra-source-files:
   readme.md
-  stack.yaml
 source-repository head
   type:
     git
@@ -57,12 +55,13 @@
     -Wincomplete-record-updates
     -Wincomplete-uni-patterns
     -Wredundant-constraints
+    -fwrite-ide-info
+    -hiedir=.hie
   build-depends:
     base >=4.11 && <5,
     adjunctions >=4.0 && <5,
-    deepseq >=1.4.2.0 && <2,
     distributive >=0.4 && <0.7,
-    numhask >= 0.7 && <0.8,
+    numhask >= 0.8 && <0.9,
     vector >=0.10 && <0.13,
   exposed-modules:
     NumHask.Array
@@ -80,7 +79,14 @@
   default-extensions:
   build-depends:
     base >=4.11 && <5,
-    doctest >=0.13 && <0.17,
-    numhask-array,
-    numhask >=0.7 && <0.8,
+    doctest >=0.13 && <0.19,
+    numhask-array
   default-language: Haskell2010
+  ghc-options:
+    -Wall
+    -Wcompat
+    -Wincomplete-record-updates
+    -Wincomplete-uni-patterns
+    -Wredundant-constraints
+    -fwrite-ide-info
+    -hiedir=.hie
diff --git a/src/NumHask/Array/Dynamic.hs b/src/NumHask/Array/Dynamic.hs
--- a/src/NumHask/Array/Dynamic.hs
+++ b/src/NumHask/Array/Dynamic.hs
@@ -1,17 +1,18 @@
 {-# LANGUAGE DataKinds #-}
-{-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RebindableSyntax #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StrictData #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE NoStarIsType #-}
 {-# OPTIONS_GHC -Wno-redundant-constraints #-}
 {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
+{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
 
 -- | Arrays with a dynamic shape.
 module NumHask.Array.Dynamic
@@ -42,6 +43,7 @@
     append,
     reorder,
     expand,
+    apply,
     contract,
     dot,
     mult,
@@ -65,11 +67,11 @@
   )
 where
 
-import Data.List ((!!))
+import Data.List (intercalate)
 import qualified Data.Vector as V
 import GHC.Show (Show (..))
 import NumHask.Array.Shape
-import NumHask.Prelude as P hiding (product, transpose)
+import NumHask.Prelude as P hiding (product)
 
 -- $setup
 -- >>> :set -XDataKinds
@@ -92,7 +94,7 @@
 --   [17, 18, 19, 20],
 --   [21, 22, 23, 24]]]
 data Array a = Array {shape :: [Int], unArray :: V.Vector a}
-  deriving (Eq, Ord, NFData, Generic)
+  deriving (Eq, Ord, Generic)
 
 instance Functor Array where
   fmap f (Array s a) = Array s (V.map f a)
@@ -109,7 +111,7 @@
     where
       go n a'@(Array l' m) =
         case length l' of
-          0 -> maybe (throw (NumHaskException "empty scalar")) GHC.Show.show (head m)
+          0 -> GHC.Show.show (V.head m)
           1 -> "[" ++ intercalate ", " (GHC.Show.show <$> V.toList m) ++ "]"
           x ->
             "["
@@ -182,8 +184,8 @@
 -- [[1, 0],
 --  [0, 1],
 --  [0, 0]]
-ident :: (Num a) => [Int] -> Array a
-ident ds = tabulate ds (bool 0 1 . isDiag)
+ident :: (Additive a, Multiplicative a) => [Int] -> Array a
+ident ds = tabulate ds (bool zero one . isDiag)
   where
     isDiag [] = True
     isDiag [_] = True
@@ -406,6 +408,30 @@
 expand f a b = tabulate ((++) (shape a) (shape b)) (\i -> f (index a (take r i)) (index b (drop r i)))
   where
     r = rank (shape a)
+
+-- | Apply an array of functions to each array of values.
+--
+-- This is in the spirit of the applicative functor operation (<*>).
+--
+-- > expand f a b == apply (fmap f a) b
+--
+-- >>> apply ((*) <$> v) v
+-- [[1, 2, 3],
+--  [2, 4, 6],
+--  [3, 6, 9]]
+--
+-- >>> let b = fromFlatList [2,3] [1..6] :: Array Int
+-- >>> contract sum [1,2] (apply (fmap (*) b) (transpose b))
+-- [[14, 32],
+--  [32, 77]]
+--
+apply ::
+  Array (a -> b) ->
+  Array a ->
+  Array b
+apply f a = tabulate ((++) (shape f) (shape a)) (\i -> index f (take r i) (index a (drop r i)))
+  where
+    r = rank (shape f)
 
 -- | Contract an array by applying the supplied (folding) function on diagonal elements of the dimensions.
 --
diff --git a/src/NumHask/Array/Fixed.hs b/src/NumHask/Array/Fixed.hs
--- a/src/NumHask/Array/Fixed.hs
+++ b/src/NumHask/Array/Fixed.hs
@@ -7,14 +7,16 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE PolyKinds #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RebindableSyntax #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StrictData #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeOperators #-}
-{-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE NoStarIsType #-}
 {-# OPTIONS_GHC -Wno-redundant-constraints #-}
 {-# OPTIONS_GHC -fno-warn-unused-imports #-}
+{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
 
 -- | Arrays with a fixed shape.
 module NumHask.Array.Fixed
@@ -43,6 +45,7 @@
     append,
     reorder,
     expand,
+    apply,
     contract,
     dot,
     mult,
@@ -76,7 +79,6 @@
   )
 where
 
-import Control.Category (id)
 import Data.Distributive (Distributive (..))
 import Data.Functor.Rep
 import Data.List ((!!))
@@ -86,7 +88,8 @@
 import GHC.TypeLits
 import qualified NumHask.Array.Dynamic as D
 import NumHask.Array.Shape
-import NumHask.Prelude as P hiding (identity, transpose)
+import NumHask.Prelude as P hiding (toList)
+import Data.Proxy
 
 -- $setup
 -- >>> :set -XDataKinds
@@ -110,8 +113,8 @@
 --   [21, 22, 23, 24]]]
 --
 -- >>> [1,2,3] :: Array '[2,2] Int
--- [[*** Exception: NumHaskException {errorMessage = "shape mismatch"}
-newtype Array s a = Array {unArray :: V.Vector a} deriving (Eq, Ord, NFData, Functor, Foldable, Generic, Traversable)
+-- *** Exception: NumHaskException {errorMessage = "shape mismatch"}
+newtype Array s a = Array {unArray :: V.Vector a} deriving (Eq, Ord, Functor, Foldable, Generic, Traversable)
 
 instance (HasShape s, Show a) => Show (Array s a) where
   show a = GHC.Show.show (toDynamic a)
@@ -241,7 +244,7 @@
 
 -- | convert to a dynamic array with shape at the value level.
 toDynamic :: (HasShape s) => Array s a -> D.Array a
-toDynamic a = D.fromFlatList (shape a) (P.toList a)
+toDynamic a = D.fromFlatList (shape a) (toList a)
 
 -- | Use a dynamic array in a fixed context.
 --
@@ -631,6 +634,44 @@
   where
     r = rank (shape a)
 
+-- | Apply an array of functions to each array of values.
+--
+-- This is in the spirit of the applicative functor operation (<*>).
+--
+-- > expand f a b == apply (fmap f a) b
+--
+-- >>> apply ((*) <$> v) v
+-- [[1, 2, 3],
+--  [2, 4, 6],
+--  [3, 6, 9]]
+--
+-- Arrays can't be applicative functors in haskell because the changes in shape are reflected in the types.
+--
+-- > :t apply
+-- apply
+--   :: (HasShape s, HasShape s', HasShape (s ++ s')) =>
+--      Array s (a -> b) -> Array s' a -> Array (s ++ s') b
+-- > :t (<*>)
+-- (<*>) :: Applicative f => f (a -> b) -> f a -> f b
+--
+-- >>> let b = [1..6] :: Array '[2,3] Int
+-- >>> contract sum (Proxy :: Proxy '[1,2]) (apply (fmap (*) b) (transpose b))
+-- [[14, 32],
+--  [32, 77]]
+--
+apply ::
+  forall s s' a b.
+  ( HasShape s,
+    HasShape s',
+    HasShape ((++) s s')
+  ) =>
+  Array s (a -> b) ->
+  Array s' a ->
+  Array ((++) s s') b
+apply f a = tabulate (\i -> index f (take r i) (index a (drop r i)))
+  where
+    r = rank (shape f)
+
 -- | Contract an array by applying the supplied (folding) function on diagonal elements of the dimensions.
 --
 -- This generalises a tensor contraction by allowing the number of contracting diagonals to be other than 2, and allowing a binary operator other than multiplication.
@@ -982,3 +1023,4 @@
     n = fromIntegral $ natVal @n Proxy
     k = fromIntegral $ natVal @k Proxy
 {-# INLINE mmult #-}
+
diff --git a/src/NumHask/Array/Shape.hs b/src/NumHask/Array/Shape.hs
--- a/src/NumHask/Array/Shape.hs
+++ b/src/NumHask/Array/Shape.hs
@@ -4,15 +4,18 @@
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RebindableSyntax #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StrictData #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE TypeInType #-}
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE UndecidableInstances #-}
-{-# LANGUAGE NoImplicitPrelude #-}
 {-# LANGUAGE NoStarIsType #-}
 {-# OPTIONS_GHC -Wall #-}
+{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}
+{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
 
 -- | Functions for manipulating shape. The module tends to supply equivalent functionality at type-level and value-level with functions of the same name (except for capitalization).
 module NumHask.Array.Shape
@@ -74,10 +77,11 @@
   )
 where
 
-import Data.List ((!!))
 import Data.Type.Bool
 import GHC.TypeLits as L
 import NumHask.Prelude as P hiding (Last, minimum)
+import Data.Proxy
+import Data.Type.Equality
 
 -- | The Shape type holds a [Nat] at type level and the equivalent [Int] at value level.
 -- Using [Int] as the index for an array nicely represents the practical interests and constraints downstream of this high-level API: densely-packed numbers (reals or integrals), indexed and layered.
@@ -166,7 +170,7 @@
 checkIndexes is n = all (`checkIndex` n) is
 
 type family CheckIndexes (i :: [Nat]) (n :: Nat) :: Bool where
-  CheckIndexes '[] n = 'True
+  CheckIndexes '[] _ = 'True
   CheckIndexes (i : is) n = CheckIndex i n && CheckIndexes is n
 
 -- | dimension i is the i'th dimension of a Shape
@@ -320,7 +324,7 @@
     (s !! i) ': TakeIndexes s is
 
 type family (a :: [k]) !! (b :: Nat) :: k where
-  (!!) '[] i = L.TypeError ('Text "Index Underflow")
+  (!!) '[] _ = L.TypeError ('Text "Index Underflow")
   (!!) (x : _) 0 = x
   (!!) (_ : xs) i = (!!) xs (i - 1)
 
@@ -395,8 +399,8 @@
       ~ 'True
 
 -- | remove 1's from a list
-squeeze' :: (Eq a, Num a) => [a] -> [a]
-squeeze' = filter (/= 1)
+squeeze' :: (Eq a, Multiplicative a) => [a] -> [a]
+squeeze' = filter (/= one)
 
 type family Squeeze (a :: [Nat]) where
   Squeeze '[] = '[]
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
--- a/stack.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-resolver: nightly-2020-11-19
-
-packages:
-  - .
-
-extra-deps:
-  - numhask-0.7.0.0
-  - protolude-0.3.0
-  - random-1.2.0
-  - splitmix-0.1.0.3
diff --git a/test/test.hs b/test/test.hs
--- a/test/test.hs
+++ b/test/test.hs
@@ -1,20 +1,19 @@
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RebindableSyntax #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# OPTIONS_GHC -Wall #-}
 
 module Main where
 
-import NumHask.Prelude as P
+import Prelude as P
 import Test.DocTest
 
 main :: IO ()
 main = do
-  putStrLn ("NumHask.Array.Fixed DocTest turned on" :: Text)
+  putStrLn "NumHask.Array.Fixed DocTest turned on"
   doctest ["src/NumHask/Array/Fixed.hs"]
-  putStrLn ("NumHask.Array.Shape DocTest turned on" :: Text)
+  putStrLn "NumHask.Array.Shape DocTest turned on"
   doctest ["src/NumHask/Array/Shape.hs"]
-  putStrLn ("NumHask.Array.Dynamic DocTest turned on" :: Text)
+  putStrLn "NumHask.Array.Dynamic DocTest turned on"
   doctest ["src/NumHask/Array/Dynamic.hs"]
