From 59af31cdd76062df046f6dbfad7456b3e36cfd42 Mon Sep 17 00:00:00 2001 From: Kiana Sheibani Date: Sat, 25 Jun 2022 00:58:36 -0400 Subject: [PATCH] Add documentation --- src/Data/NumIdr/Array.idr | 1 + src/Data/NumIdr/Array/Array.idr | 89 ++++++++++++++++++++++++++++---- src/Data/NumIdr/Array/Coords.idr | 2 +- src/Data/NumIdr/Array/Order.idr | 1 - src/Data/NumIdr/Homogeneous.idr | 25 +++++++-- src/Data/NumIdr/Matrix.idr | 35 +++++++++---- src/Data/NumIdr/Multiply.idr | 21 +++++++- src/Data/NumIdr/PrimArray.idr | 16 +++--- src/Data/NumIdr/Scalar.idr | 2 + src/Data/NumIdr/Vector.idr | 33 +++++++++++- 10 files changed, 188 insertions(+), 37 deletions(-) diff --git a/src/Data/NumIdr/Array.idr b/src/Data/NumIdr/Array.idr index fecd96b..fd81478 100644 --- a/src/Data/NumIdr/Array.idr +++ b/src/Data/NumIdr/Array.idr @@ -1,5 +1,6 @@ module Data.NumIdr.Array +import public Data.NP import public Data.NumIdr.Array.Array import public Data.NumIdr.Array.Coords import public Data.NumIdr.Array.Order diff --git a/src/Data/NumIdr/Array/Array.idr b/src/Data/NumIdr/Array/Array.idr index 8fbca93..704b9e5 100644 --- a/src/Data/NumIdr/Array/Array.idr +++ b/src/Data/NumIdr/Array/Array.idr @@ -4,6 +4,7 @@ import Data.List import Data.List1 import Data.Vect import Data.Zippable +import Data.NP import Data.NumIdr.Multiply import Data.NumIdr.PrimArray import Data.NumIdr.Array.Order @@ -63,6 +64,7 @@ strides : Array {rk} s a -> Vect rk Nat strides (MkArray _ sts _ _) = sts ||| The total number of elements of the array +||| ||| This is equivalent to `product s`. export size : Array s a -> Nat @@ -175,6 +177,26 @@ export fromStream : (s : Vect rk Nat) -> Stream a -> Array s a fromStream s = fromStream' s COrder + +||| Create an array given a function to generate its elements. +||| +||| @ s The shape of the constructed array +||| @ ord The order to interpret the elements +export +fromFunctionNB' : (s : Vect rk Nat) -> (ord : Order) -> (Vect rk Nat -> a) -> Array s a +fromFunctionNB' s ord f = let sts = calcStrides ord s + in MkArray ord sts s (unsafeFromIns (product s) $ + map (\is => (getLocation' sts is, f is)) $ getAllCoords' s) + +||| Create an array given a function to generate its elements. +||| To specify the order of the array, use `fromFunctionNB'`. +||| +||| @ s The shape of the constructed array +||| @ ord The order to interpret the elements +export +fromFunctionNB : (s : Vect rk Nat) -> (Vect rk Nat -> a) -> Array s a +fromFunctionNB s = fromFunctionNB' s COrder + ||| Create an array given a function to generate its elements. ||| ||| @ s The shape of the constructed array @@ -219,32 +241,39 @@ array v = MkArray COrder (calcStrides COrder s) s (fromList $ collapse v) -- Indexing -------------------------------------------------------------------------------- -infix 10 !! -infix 10 !? +infixl 10 !! +infixl 10 !? infixl 11 !!.. -infix 11 !?.. +infixl 11 !?.. -||| Index the array using the given `Coords` object. +||| Index the array using the given coordinates. export index : Coords s -> Array s a -> a index is arr = index (getLocation (strides arr) is) (getPrim arr) +||| Index the array using the given coordinates. +||| +||| This is the operator form of `index`. export (!!) : Array s a -> Coords s -> a (!!) = flip index -- TODO: Create set/update at index functions +||| Update the value at the given coordinates using the function. export indexUpdate : Coords s -> (a -> a) -> Array s a -> Array s a indexUpdate is f (MkArray ord sts s arr) = MkArray ord sts s (updateAt (getLocation sts is) f arr) +||| Set the value at the given coordinates to the given value. export indexSet : Coords s -> a -> Array s a -> Array s a indexSet is = indexUpdate is . const +||| Index the array using the given coordinates, returning `Nothing` if the +||| coordinates are out of bounds. export indexNB : Vect rk Nat -> Array {rk} s a -> Maybe a indexNB is arr with (viewShape arr) @@ -252,21 +281,29 @@ indexNB is arr with (viewShape arr) then Just $ index (getLocation' (strides arr) is) (getPrim arr) else Nothing +||| Index the array using the given coordinates, returning `Nothing` if the +||| coordinates are out of bounds. +||| +||| This is the operator form of `indexNB`. export (!?) : Array {rk} s a -> Vect rk Nat -> Maybe a (!?) = flip indexNB +||| Update the value at the given coordinates using the function. The array is +||| returned unchanged if the coordinate is out of bounds. export indexUpdateNB : Vect rk Nat -> (a -> a) -> Array {rk} s a -> Array s a indexUpdateNB is f (MkArray ord sts s arr) = MkArray ord sts s (updateAt (getLocation' sts is) f arr) +||| Set the value at the given coordinates to the given value. The array is +||| returned unchanged if the coordinate is out of bounds. export indexSetNB : Vect rk Nat -> a -> Array {rk} s a -> Array s a indexSetNB is = indexUpdateNB is . const -||| Index the array using the given `CoordsRange` object. +||| Index the array using the given range of coordinates, returning a new array. export indexRange : (rs : CoordsRange s) -> Array s a -> Array (newShape rs) a indexRange rs arr with (viewShape arr) @@ -281,6 +318,9 @@ indexRange rs arr with (viewShape arr) where s' : Vect ? Nat s' = newShape rs +||| Index the array using the given range of coordinates, returning a new array. +||| +||| This is the operator form of `indexRange`. export (!!..) : Array s a -> (rs : CoordsRange s) -> Array (newShape rs) a arr !!.. rs = indexRange rs arr @@ -320,10 +360,20 @@ reorder ord' arr with (viewShape arr) getAllCoords' s) +||| Resize the array to a new shape, preserving the coordinates of the original +||| elements. New coordinates are filled with a default value. +||| +||| @ s' The shape to resize the array to +||| @ def The default value to fill the array with export -resize : (s' : Vect rk Nat) -> a -> Array {rk} s a -> Array s' a -resize s' x arr = fromFunction' s' (getOrder arr) (fromMaybe x . (arr !?) . toNB) +resize : (s' : Vect rk Nat) -> (def : a) -> Array {rk} s a -> Array s' a +resize s' def arr = fromFunction' s' (getOrder arr) (fromMaybe def . (arr !?) . toNB) +||| Resize the array to a new shape, preserving the coordinates of the original +||| elements. This function requires a proof that the new shape is strictly +||| smaller than the current shape of the array. +||| +||| @ s' The shape to resize the array to export -- TODO: Come up with a solution that doesn't use `believe_me` or trip over some -- weird bug in the type-checker @@ -332,20 +382,26 @@ resizeLTE : (s' : Vect rk Nat) -> (0 ok : NP Prelude.id (zipWith LTE s' s)) => resizeLTE s' arr = resize s' (believe_me ()) arr +||| List all of the values in an array along with their coordinates. export enumerateNB : Array {rk} s a -> List (Vect rk Nat, a) enumerateNB (MkArray _ sts sh p) = map (\is => (is, index (getLocation' sts is) p)) (getAllCoords' sh) - +||| List all of the values in an array along with their coordinates. export -enumerate : Array {rk} s a -> List (Coords s, a) +enumerate : Array s a -> List (Coords s, a) enumerate arr with (viewShape arr) _ | Shape s = map (\is => (is, index is arr)) (getAllCoords s) + +||| Join two arrays along a particular axis, e.g. combining two matrices +||| vertically or horizontally. The arrays must have the same shape on all other axes. +||| +||| @ axis The axis to join the arrays on export concat : (axis : Fin rk) -> Array {rk} s a -> Array (replaceAt axis d s) a -> - Array (updateAt axis (+d) s) a + Array (updateAt axis (+d) s) a concat axis a b = let sA = shape a sB = shape b dA = index axis sA @@ -357,6 +413,9 @@ concat axis a b = let sA = shape a -- TODO: prove that the type-level shape and `s` are equivalent in believe_me $ MkArray COrder sts s (unsafeFromIns (product s) ins) +||| Stack multiple arrays along a new axis, e.g. stacking vectors to form a matrix. +||| +||| @ axis The axis to stack the arrays along export stack : {s : _} -> (axis : Fin (S rk)) -> Vect n (Array {rk} s a) -> Array (insertAt axis n s) a stack axis arrs = rewrite sym (lengthCorrect arrs) in @@ -415,7 +474,7 @@ export export {s : _} -> Monad (Array s) where - join arr = fromFunction s (\is => index is $ index is arr) + join arr = fromFunction s (\is => arr !! is !! is) -- Foldable and Traversable operate on the primitive array directly. This means @@ -466,6 +525,7 @@ export negate = map negate (-) = zipWith (-) +export {s : _} -> Fractional a => Fractional (Array s a) where recip = map recip (/) = zipWith (/) @@ -508,23 +568,30 @@ Show a => Show (Array s a) where -------------------------------------------------------------------------------- +||| Linearly interpolate between two arrays. export lerp : Neg a => a -> Array s a -> Array s a -> Array s a lerp t a b = zipWith (+) (a *. (1 - t)) (b *. t) +||| Calculate the square of an array's Eulidean norm. export normSq : Num a => Array s a -> a normSq arr = sum $ zipWith (*) arr arr +||| Calculate an array's Eucliean norm. export norm : Array s Double -> Double norm = sqrt . normSq +||| Normalize the array to a norm of 1. +||| +||| If the array contains all zeros, then it is returned unchanged. export normalize : Array s Double -> Array s Double normalize arr = if all (==0) arr then arr else map (/ norm arr) arr +||| Calculate the Lp-norm of an array. export pnorm : (p : Double) -> Array s Double -> Double pnorm p = (`pow` recip p) . sum . map (`pow` p) diff --git a/src/Data/NumIdr/Array/Coords.idr b/src/Data/NumIdr/Array/Coords.idr index 2f8f3bd..8dd0fae 100644 --- a/src/Data/NumIdr/Array/Coords.idr +++ b/src/Data/NumIdr/Array/Coords.idr @@ -2,7 +2,7 @@ module Data.NumIdr.Array.Coords import Data.Either import Data.Vect -import public Data.NP +import Data.NP %default total diff --git a/src/Data/NumIdr/Array/Order.idr b/src/Data/NumIdr/Array/Order.idr index c277771..633d778 100644 --- a/src/Data/NumIdr/Array/Order.idr +++ b/src/Data/NumIdr/Array/Order.idr @@ -29,7 +29,6 @@ Eq Order where FOrder == COrder = False - scanr : (el -> res -> res) -> res -> Vect len el -> Vect (S len) res scanr _ q0 [] = [q0] scanr f q0 (x::xs) = f x (head qs) :: qs diff --git a/src/Data/NumIdr/Homogeneous.idr b/src/Data/NumIdr/Homogeneous.idr index 32ba267..2cd7e8a 100644 --- a/src/Data/NumIdr/Homogeneous.idr +++ b/src/Data/NumIdr/Homogeneous.idr @@ -8,14 +8,33 @@ import Data.NumIdr.Matrix %default total -public export -HVector : Nat -> Type -> Type -HVector n = Vector (S n) +||| A homogeneous matrix type. +||| +||| Homogeneous matrices are matrices that encode an affine transformation, as +||| opposed to the more restriced linear transformations of normal matrices. +||| Their multiplication is identical to regular matrix multiplication, but the +||| extra dimension in each axis allows the homogeneous matrix to store a +||| translation vector that is applied during multiplication. +||| +||| ```hmatrix mat tr *. v == mat *. v + tr``` public export HMatrix : Nat -> Nat -> Type -> Type HMatrix m n = Matrix (S m) (S n) +||| A synonym for a square homogeneous matrix. +public export +HMatrix' : Nat -> Type -> Type +HMatrix' n = HMatrix n n + +||| An `n`-dimensional homogeneous vector type. +||| +||| Homogeneous vectors are vectors intended to be multiplied with homogeneous +||| matrices (see `HMatrix`). They have an extra dimension compared to regular +||| vectors. +public export +HVector : Nat -> Type -> Type +HVector n = Vector (S n) export diff --git a/src/Data/NumIdr/Matrix.idr b/src/Data/NumIdr/Matrix.idr index 9e33de6..5d5c8a5 100644 --- a/src/Data/NumIdr/Matrix.idr +++ b/src/Data/NumIdr/Matrix.idr @@ -8,10 +8,12 @@ import Data.NumIdr.Vector %default total +||| A matrix is a rank-2 array. public export Matrix : Nat -> Nat -> Type -> Type Matrix m n = Array [m,n] +||| A synonym for a square matrix with dimensions of length `n`. public export Matrix' : Nat -> Type -> Type Matrix' n = Matrix n n @@ -22,26 +24,30 @@ Matrix' n = Matrix n n -------------------------------------------------------------------------------- +||| Construct a matrix with the given order and elements. export matrix' : {m, n : _} -> Order -> Vect m (Vect n a) -> Matrix m n a matrix' ord x = array' [m,n] ord x +||| Construct a matrix with the given elements. export matrix : {m, n : _} -> Vect m (Vect n a) -> Matrix m n a matrix = matrix' COrder +||| Construct a matrix with a specific value along the diagonal. +||| +||| @ diag The value to repeat along the diagonal +||| @ other The value to repeat elsewhere export repeatDiag : {m, n : _} -> (diag, other : a) -> Matrix m n a -repeatDiag d o = fromFunction [m,n] - (\[i,j] => if i `eq` j then d else o) - where - eq : {0 m,n : Nat} -> Fin m -> Fin n -> Bool - eq FZ FZ = True - eq (FS x) (FS y) = eq x y - eq FZ (FS _) = False - eq (FS _) FZ = False +repeatDiag d o = fromFunctionNB [m,n] + (\[i,j] => if i == j then d else o) +||| Construct a matrix given its diagonal elements. +||| +||| @ diag The elements of the matrix's diagonal +||| @ other The value to repeat elsewhere export fromDiag : {m, n : _} -> (diag : Vect (minimum m n) a) -> (other : a) -> Matrix m n a fromDiag ds o = fromFunction [m,n] (\[i,j] => maybe o (`index` ds) $ i `eq` j) @@ -53,15 +59,18 @@ fromDiag ds o = fromFunction [m,n] (\[i,j] => maybe o (`index` ds) $ i `eq` j) eq (FS _) FZ = Nothing +||| The `n`-dimensional identity matrix. export identity : Num a => {n : _} -> Matrix' n a identity = repeatDiag 1 0 +||| Calculate the matrix that scales a vector by the given value. export scaling : Num a => {n : _} -> a -> Matrix' n a scaling x = repeatDiag x 0 +||| Calculate the rotation matrix of an angle. export rotation2D : Double -> Matrix' 2 Double rotation2D a = matrix [[cos a, - sin a], [sin a, cos a]] @@ -72,19 +81,24 @@ rotation2D a = matrix [[cos a, - sin a], [sin a, cos a]] -------------------------------------------------------------------------------- +||| Index the matrix at the given coordinates. export index : Fin m -> Fin n -> Matrix m n a -> a index m n = index [m,n] +||| Index the matrix at the given coordinates, returning `Nothing` if the +||| coordinates are out of bounds. export indexNB : Nat -> Nat -> Matrix m n a -> Maybe a indexNB m n = indexNB [m,n] +||| Return a row of the matrix as a vector. export getRow : Fin m -> Matrix m n a -> Vector n a getRow r mat = rewrite sym (minusZeroRight n) in indexRange [One r, All] mat +||| Return a column of the matrix as a vector. export getColumn : Fin n -> Matrix m n a -> Vector m a getColumn c mat = rewrite sym (minusZeroRight m) in indexRange [All, One c] mat @@ -94,19 +108,22 @@ getColumn c mat = rewrite sym (minusZeroRight m) in indexRange [All, One c] mat -- Operations -------------------------------------------------------------------------------- +||| Concatenate two matrices vertically. export vconcat : Matrix m n a -> Matrix m' n a -> Matrix (m + m') n a vconcat = concat 0 +||| Concatenate two matrices horizontally. export hconcat : Matrix m n a -> Matrix m n' a -> Matrix m (n + n') a hconcat = concat 1 +||| Calculate the kronecker product of two vectors as a matrix. export kronecker : Num a => Vector m a -> Vector n a -> Matrix m n a kronecker a b with (viewShape a, viewShape b) - _ | (Shape [m], Shape [n]) = fromFunction [m,n] (\[i,j] => index i a * index j b) + _ | (Shape [m], Shape [n]) = fromFunction [m,n] (\[i,j] => a !! i * b !! j) -------------------------------------------------------------------------------- diff --git a/src/Data/NumIdr/Multiply.idr b/src/Data/NumIdr/Multiply.idr index b61521f..0485a1a 100644 --- a/src/Data/NumIdr/Multiply.idr +++ b/src/Data/NumIdr/Multiply.idr @@ -6,32 +6,49 @@ module Data.NumIdr.Multiply infixr 9 *. infixr 10 ^ -||| A generalized multiplication/transformation operator. This interface is +||| A generalized multiplication/application operator. This interface is ||| necessary since the standard multiplication operator is homogenous. +||| +||| All instances of this interface must collectively satisfy these axioms: +||| * If `(x *. y) *. z` is defined, then `x *. (y *. z)` is defined and equal. +||| * If `x *. (y *. z)` is defined, then `(x *. y) *. z` is defined and equal. public export interface Mult a b c | a,b where (*.) : a -> b -> c +||| An interface for monoids using the `*.` operator. +||| +||| An instance of this interface must satisfy: +||| * `x *. neutral == x` +||| * `neutral *. x == x` public export -interface (Mult a a a) => MultNeutral a where +interface Mult a a a => MultNeutral a where neutral : a +||| Multiplication forms a semigroup public export [MultSemigroup] Mult a a a => Semigroup a where (<+>) = (*.) +||| Multiplication with a neutral element forms a monoid public export [MultMonoid] MultNeutral a => Monoid a using MultSemigroup where neutral = Multiply.neutral +||| Raise a multiplicative value (e.g. a matrix or a transformation) to a natural +||| number power. public export power : MultNeutral a => Nat -> a -> a power 0 _ = neutral power 1 x = x power (S n@(S _)) x = x *. power n x +||| Raise a multiplicative value (e.g. a matrix or a transformation) to a natural +||| number power. +||| +||| This is the operator form of `power`. public export (^) : MultNeutral a => a -> Nat -> a (^) = flip power diff --git a/src/Data/NumIdr/PrimArray.idr b/src/Data/NumIdr/PrimArray.idr index f484364..665bd7b 100644 --- a/src/Data/NumIdr/PrimArray.idr +++ b/src/Data/NumIdr/PrimArray.idr @@ -35,8 +35,8 @@ export constant : Nat -> a -> PrimArray a constant size x = MkPrimArray size $ unsafePerformIO $ newArrayData size x -||| Construct an array from a list of "instructions" to write a -||| value to a particular index. +||| Construct an array from a list of "instructions" to write a value to a +||| particular index. export unsafeFromIns : Nat -> List (Nat, a) -> PrimArray a unsafeFromIns size ins = unsafePerformIO $ do @@ -44,8 +44,8 @@ unsafeFromIns size ins = unsafePerformIO $ do for_ ins $ \(i,x) => arrayDataSet i x arr pure $ MkPrimArray size arr -||| Create an array given its size and a function to generate -||| its elements by its index. +||| Create an array given its size and a function to generate its elements by +||| its index. export create : Nat -> (Nat -> a) -> PrimArray a create size f = unsafePerformIO $ do @@ -59,8 +59,8 @@ create size f = unsafePerformIO $ do = do arrayDataSet loc (f loc) arr addToArray (S loc) n arr -||| Index into a primitive array. This function is unsafe, as it -||| performs no boundary check on the index given. +||| Index into a primitive array. This function is unsafe, as it performs no +||| boundary check on the index given. export index : Nat -> PrimArray a -> a index n arr = unsafePerformIO $ arrayDataGet n $ content arr @@ -156,8 +156,8 @@ traverse : Applicative f => (a -> f b) -> PrimArray a -> f (PrimArray b) traverse f = map fromList . traverse f . toList -||| Compares two primitive arrays for equal elements. This function assumes -||| the arrays same the same length; it must not be used in any other case. +||| Compares two primitive arrays for equal elements. This function assumes the +||| arrays have the same length; it must not be used in any other case. export unsafeEq : Eq a => PrimArray a -> PrimArray a -> Bool unsafeEq a b = unsafePerformIO $ diff --git a/src/Data/NumIdr/Scalar.idr b/src/Data/NumIdr/Scalar.idr index 64db639..a6a0ca9 100644 --- a/src/Data/NumIdr/Scalar.idr +++ b/src/Data/NumIdr/Scalar.idr @@ -15,10 +15,12 @@ Scalar : Type -> Type Scalar = Array [] +||| Convert a value to a scalar. export scalar : a -> Scalar a scalar x = fromVect _ [x] +||| Unwrap the single value from a scalar. export unwrap : Scalar a -> a unwrap = index 0 . getPrim diff --git a/src/Data/NumIdr/Vector.idr b/src/Data/NumIdr/Vector.idr index 301bb9d..97c8015 100644 --- a/src/Data/NumIdr/Vector.idr +++ b/src/Data/NumIdr/Vector.idr @@ -6,11 +6,14 @@ import public Data.NumIdr.Array %default total + +||| A vector is a rank-1 array. public export Vector : Nat -> Type -> Type Vector n = Array [n] +||| The length (number of dimensions) of the vector public export dim : Vector n a -> Nat dim = head . shape @@ -25,6 +28,7 @@ dimEq v = cong head $ shapeEq v -------------------------------------------------------------------------------- +||| Construct a vector from a `Vect`. export vector : Vect n a -> Vector n a vector v = rewrite sym (lengthCorrect v) @@ -32,20 +36,28 @@ vector v = rewrite sym (lengthCorrect v) rewrite lengthCorrect v in -- there is only 1 axis rewrite multOneLeftNeutral n in v +||| Convert a vector into a `Vect`. export toVect : Vector n a -> Vect n a toVect v = believe_me $ Vect.fromList $ toList v +||| Return the `i`-th basis vector. export basis : Num a => {n : _} -> (i : Fin n) -> Vector n a basis i = fromFunction _ (\[j] => if i == j then 1 else 0) +||| Calculate the 2D unit vector with the given angle off the x-axis. export unit2D : (ang : Double) -> Vector 2 Double unit2D ang = vector [cos ang, sin ang] +||| Calculate the 3D unit vector corresponding to the given spherical coordinates, +||| where the polar axis is the z-axis. +||| +||| @ pol The polar angle of the vector +||| @ az The azimuthal angle of the vector export unit3D : (pol, az : Double) -> Vector 3 Double unit3D pol az = vector [cos az * sin pol, sin az * sin pol, cos pol] @@ -56,35 +68,49 @@ unit3D pol az = vector [cos az * sin pol, sin az * sin pol, cos pol] -- Indexing -------------------------------------------------------------------------------- -infix 10 !! -infix 10 !? +infixl 10 !! +infixl 10 !? +||| Index the vector at the given coordinate. export index : Fin n -> Vector n a -> a index n = Array.index [n] +||| Index the vector at the given coordinate. +||| +||| This is the operator form of `index`. export (!!) : Vector n a -> Fin n -> a (!!) = flip index +||| Index the vector at the given coordinate, returning `Nothing` if the +||| coordinate is out of bounds. export indexNB : Nat -> Vector n a -> Maybe a indexNB n = Array.indexNB [n] +||| Index the vector at the given coordinate, returning `Nothing` if the +||| coordinate is out of bounds. +||| +||| This is the operator form of `indexNB`. export (!?) : Vector n a -> Nat -> Maybe a (!?) = flip indexNB -- Named projections + +||| Return the x-coordinate (the first value) of the vector. export (.x) : Vector (1 + n) a -> a (.x) = index FZ +||| Return the y-coordinate (the second value) of the vector. export (.y) : Vector (2 + n) a -> a (.y) = index (FS FZ) +||| Return the z-coordinate (the third value) of the vector. export (.z) : Vector (3 + n) a -> a (.z) = index (FS (FS FZ)) @@ -109,16 +135,19 @@ swizzle p v = rewrite sym (lengthCorrect p) -------------------------------------------------------------------------------- +||| Concatenate one vector with another. export (++) : Vector m a -> Vector n a -> Vector (m + n) a (++) = concat 0 +||| Calculate the dot product of the two vectors. export dot : Num a => Vector n a -> Vector n a -> a dot = sum .: zipWith (*) +||| Calculate the perpendicular product of the two vectors. export perp : Neg a => Vector 2 a -> Vector 2 a -> a perp a b = a.x * b.y - a.y * b.x