diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Tony Day (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Tony Day nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/numhask-space.cabal b/numhask-space.cabal
new file mode 100644
--- /dev/null
+++ b/numhask-space.cabal
@@ -0,0 +1,59 @@
+name: numhask-space
+version: 0.1.1
+synopsis:
+  numerical spaces
+description:
+  Spaces as higher-kinded numbers.
+category:
+  mathematics
+homepage:
+  https://github.com/tonyday567/numhask#readme
+bug-reports:
+  https://github.com/tonyday567/numhask/issues
+author:
+  Tony Day
+maintainer:
+  tonyday567@gmail.com
+copyright:
+  Tony Day
+license:
+  BSD3
+license-file:
+  LICENSE
+build-type:
+  Simple
+cabal-version:
+  1.18
+source-repository head
+  type:
+    git
+  location:
+    https://github.com/tonyday567/numhask
+  subdir:
+    numhask-space
+library
+  hs-source-dirs:
+    src
+  default-extensions:
+    NegativeLiterals
+    OverloadedStrings
+    UnicodeSyntax
+  ghc-options:
+    -Wall
+    -Wcompat
+    -Wincomplete-record-updates
+    -Wincomplete-uni-patterns
+    -Wredundant-constraints
+  build-depends:
+      base >=4.7 && <5
+    , numhask >=0.3 && < 0.4
+    , adjunctions >=4.0 && <5
+    , semigroupoids >=5 && <6
+    , distributive >=0.2.2 && <1
+  exposed-modules:
+    NumHask.Analysis.Space
+    NumHask.Data.Range
+    NumHask.Data.RangeD
+    NumHask.Data.Rect
+  other-modules:
+  default-language: Haskell2010
diff --git a/src/NumHask/Analysis/Space.hs b/src/NumHask/Analysis/Space.hs
new file mode 100644
--- /dev/null
+++ b/src/NumHask/Analysis/Space.hs
@@ -0,0 +1,222 @@
+{-# LANGUAGE ConstrainedClassMethods #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+-- https://en.wikipedia.org/wiki/Interval_(mathematics)
+module NumHask.Analysis.Space
+  ( Space(..)
+  , Spaceable
+  , Union(..)
+  , Intersection(..)
+  , FieldSpace(..)
+  , mid
+  , project
+  , Pos(..)
+  , space1
+  , (|.|)
+  , memberOf
+  , contains
+  , disjoint
+  , (|>|)
+  , (|<|)
+  , width
+  , (+/-)
+  , monotone
+  , whole
+  , negWhole
+  , eps
+  , widen
+  , widenEps
+  )
+
+where
+
+import Data.Bool
+import NumHask.Algebra.Abstract
+import NumHask.Analysis.Metric
+import Prelude (Functor(..), Eq(..), Bool(..), Show, foldr1, Traversable(..), (.), Semigroup(..), Monoid(..))
+
+type Spaceable a = (Eq a, JoinSemiLattice a, MeetSemiLattice a)
+
+-- | a continuous set of numbers
+-- mathematics does not define a space, so library devs are free to experiment.
+--
+-- > a `contains` union a b && b `contains` union a b
+-- > lower a \/ upper a == lower a
+-- > lower a /\ upper a == upper a
+--
+class (Spaceable (Element s)) => Space s where
+
+  -- | the underlying element in the space
+  type Element s :: *
+
+  -- | lower boundary
+  lower :: s -> Element s
+
+  -- | upper boundary
+  upper :: s -> Element s
+
+  -- | space containing a single element
+  singleton :: Element s -> s
+  singleton s = s >.< s
+
+  -- | the intersection of two spaces
+  intersection :: s -> s -> s
+  intersection a b = l >.< u where
+      l = lower a /\ lower b
+      u = upper a \/ upper b
+
+  -- | the union of two spaces
+  union :: s -> s -> s
+  union a b = l >.< u where
+    l = lower a \/ lower b
+    u = upper a /\ upper b
+
+  -- | Normalise a space so that
+  -- > lower a \/ upper a == lower a
+  -- > lower a /\ upper a == upper a
+  norm :: s -> s
+  norm s = lower s ... upper s
+
+  -- | create a normalised space from two elements
+  infix 3 ...
+  (...) :: Element s -> Element s -> s
+  (...) a b = (a\/b) >.< (a/\b)
+
+  -- | create a space from two elements witjout normalising
+  infix 3 >.<
+  (>.<) :: Element s -> Element s -> s
+
+newtype Union a = Union { getUnion :: a }
+
+instance (Space a) => Semigroup (Union a) where
+  (<>) (Union a) (Union b) = Union (a `union` b)
+
+instance (BoundedJoinSemiLattice a, Space a) => Monoid (Union a) where
+  mempty = Union bottom
+
+newtype Intersection a = Intersection { getIntersection :: a }
+
+instance (Space a) => Semigroup (Intersection a) where
+  (<>) (Intersection a) (Intersection b) = Intersection (a `union` b)
+
+instance (BoundedMeetSemiLattice a, Space a) => Monoid (Intersection a) where
+  mempty = Intersection top
+
+-- | a space that can be divided neatly
+--
+class (Space s, Subtractive (Element s), Field (Element s)) => FieldSpace s where
+  type Grid s :: *
+
+  -- | create equally-spaced elements across a space
+  grid :: Pos -> s -> Grid s -> [Element s]
+
+  -- | create equally-spaced spaces from a space
+  gridSpace :: s -> Grid s -> [s]
+
+-- | Pos suggests where points should be placed in forming a grid across a field space.
+data Pos = OuterPos | InnerPos | LowerPos | UpperPos | MidPos deriving (Show, Eq)
+
+-- | mid-point of the space
+mid :: (Space s, Field (Element s)) => s -> Element s
+mid s = (lower s + upper s)/two
+
+-- | project a data point from one space to another, preserving relative position
+--
+-- > project o n (lower o) = lower n
+-- > project o n (upper o) = upper n
+-- > project a a x = x
+--
+project :: (Space s, Field (Element s), Subtractive (Element s)) => s -> s -> Element s -> Element s
+project s0 s1 p =
+  ((p-lower s0)/(upper s0-lower s0)) * (upper s1-lower s1) + lower s1
+
+-- | the containing space of a non-empty Foldable
+space1 :: (Space s, Traversable f) => f (Element s) -> s
+space1 = foldr1 union . fmap singleton
+
+-- | is an element in the space
+infixl 7 |.|
+(|.|) :: (Space s) => Element s -> s -> Bool
+(|.|) a s = (a `joinLeq` lower s) && (upper s `meetLeq` a)
+
+memberOf :: (Space s) => Element s -> s -> Bool
+memberOf = (|.|)
+
+-- | distance between boundaries
+width :: (Space s, Subtractive (Element s)) => s -> Element s
+width s = upper s - lower s
+
+-- | create a space centered on a plus or minus b
+infixl 6 +/-
+(+/-) :: (Space s, Subtractive (Element s)) => Element s -> Element s -> s
+a +/- b = a - b ... a + b
+
+-- | is a space contained within another?
+contains :: (Space s) => s -> s -> Bool
+contains s0 s1 =
+  lower s1 |.| s0 &&
+  upper s1 |.| s0
+
+-- | are two spaces disjoint?
+disjoint :: (Space s) => s -> s -> Bool
+disjoint s0 s1 = s0 |>| s1 || s0 |<| s1
+
+-- | is one space completely above the other
+infixl 7 |>|
+(|>|) :: (Space s) => s -> s -> Bool
+(|>|) s0 s1 =
+  lower s0 `joinLeq` upper s1
+
+-- | is one space completely below the other
+infixl 7 |<|
+(|<|) :: (Space s) => s -> s -> Bool
+(|<|) s0 s1 =
+  lower s1 `meetLeq` upper s0
+
+-- | lift a monotone function (increasing or decreasing) over a given space
+monotone :: (Space a, Space b) => (Element a -> Element b) -> a -> b
+monotone f s = space1 [f (lower s), f (upper s)]
+
+-- | a big, big space
+whole ::
+  ( Space s
+  , BoundedJoinSemiLattice (Element s)
+  , BoundedMeetSemiLattice (Element s)
+  ) => s
+whole = bottom ... top
+
+-- | a negative space
+negWhole ::
+  ( Space s
+  , BoundedJoinSemiLattice (Element s)
+  , BoundedMeetSemiLattice (Element s)
+  ) => s
+negWhole = top >.< bottom
+
+-- | a small space
+eps ::
+    ( Space s
+    , Epsilon (Element s)
+    , Multiplicative (Element s)
+    )
+    => Element s -> Element s -> s
+eps accuracy a = a +/- (accuracy * a * epsilon)
+
+-- | widen a space
+widen ::
+    ( Space s
+    , Subtractive (Element s))
+    => Element s -> s -> s
+widen a s = (lower s - a) >.< (upper s + a)
+
+-- | widen by a small amount
+widenEps ::
+    ( Space s
+    , Epsilon (Element s)
+    , Multiplicative (Element s))
+    => Element s -> s -> s
+widenEps accuracy = widen (accuracy * epsilon)
diff --git a/src/NumHask/Data/Range.hs b/src/NumHask/Data/Range.hs
new file mode 100644
--- /dev/null
+++ b/src/NumHask/Data/Range.hs
@@ -0,0 +1,259 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE RebindableSyntax #-}
+{-# LANGUAGE RoleAnnotations #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -Wall #-}
+
+-- | An Space with no empty, a semigroup based on a convex hull union, and a monoid on a negative space.
+module NumHask.Data.Range
+  ( Range(..)
+  , pattern Range
+  , gridSensible
+ ) where
+
+import Data.Functor.Rep
+import Data.Distributive as D
+import Data.Bool (bool, not)
+import Data.Functor.Apply (Apply(..))
+import Data.Functor.Classes
+import Data.Semigroup.Foldable (Foldable1(..))
+import Data.Semigroup.Traversable (Traversable1(..))
+import GHC.Exts
+import GHC.Generics (Generic)
+import NumHask.Algebra.Abstract as A
+import NumHask.Analysis.Metric
+import NumHask.Analysis.Space as S
+import NumHask.Data.Integral
+import NumHask.Data.Rational
+import Prelude (Eq(..), Ord(..), Show(..), Integer, Bool(..), Foldable(..), Functor, Traversable(..), Applicative, pure, (<*>), (.), otherwise, (&&), fmap, (<$>), Semigroup(..), Monoid(..), zipWith, drop, filter, ($), id)
+
+-- $setup
+-- >>> :set -XNoImplicitPrelude
+-- >>> :set -XFlexibleContexts
+
+-- | A continuous range over type a
+--
+-- >>> let a = Range (-1) 1
+-- >>> a
+-- Range -1 1
+-- >>> fmap (+1) (Range 1 2)
+-- Range 2 3
+-- >>> one :: Range Double
+-- Range -0.5 0.5
+-- >>> zero :: Range Double
+-- Range Infinity -Infinity
+
+-- | as a Field instance
+--
+-- >>> Range 0 1 + zero
+-- Range 0.0 1.0
+-- >>> Range 0 1 + Range 2 3
+-- Range 0.0 3.0
+-- >>> Range 1 1 - one
+-- Range 0.5 1.0
+-- >>> Range 0 1 * one
+-- Range 0.0 1.0
+-- >>> Range 0 1 / one
+-- Range 0.0 1.0
+-- >>> abs (Range 1 0)
+-- Range 0.0 1.0
+-- >>> sign (Range 1 0) == negate one
+-- True
+--
+-- Idempotent
+--
+-- >>> Range 0 2 + Range 0 2
+-- Range 0.0 2.0
+--
+-- as a space instance
+--
+-- >>> NumHask.Space.project (Range 0 1) (Range 1 4) 0.5
+-- 2.5
+-- >>> NumHask.Space.grid NumHask.Space.OuterPos (Range 0 10) 5
+-- [0.0,2.0,4.0,6.0,8.0,10.0]
+-- >>> NumHask.Space.gridSpace (Range 0 1) 4
+-- [Range 0.0 0.25,Range 0.25 0.5,Range 0.5 0.75,Range 0.75 1.0]
+-- >>> gridSensible NumHask.Space.OuterPos (Range (-12.0) 23.0) 6
+-- [-10.0,-5.0,0.0,5.0,10.0,15.0,20.0]
+
+newtype Range a = Range' (a,a)
+  deriving (Eq, Generic)
+
+-- not sure if this is correct or needed
+type role Range representational
+
+-- | A tuple is the preferred concrete implementation of a Range, due to many libraries having substantial optimizations for tuples already (eg 'Vector').  'Pattern Synonyms' allow us to recover a constructor without the need for tuple syntax.
+pattern Range :: a -> a -> Range a
+pattern Range a b = Range' (a,b)
+{-# COMPLETE Range#-}
+
+instance (Show a) => Show (Range a) where
+    show (Range a b) = "Range " <> show a <> " " <> show b
+
+instance Eq1 Range where
+    liftEq f (Range a b) (Range c d) = f a c && f b d
+
+instance Show1 Range where
+    liftShowsPrec sp _ d (Range' (a,b)) = showsBinaryWith sp sp "Range" d a b
+
+instance Functor Range where
+    fmap f (Range a b) = Range (f a) (f b)
+
+instance Apply Range where
+  Range fa fb <.> Range a b = Range (fa a) (fb b)
+
+instance Applicative Range where
+    pure a = Range a a
+    (Range fa fb) <*> Range a b = Range (fa a) (fb b)
+
+instance Foldable Range where
+  foldMap f (Range a b) = f a `mappend` f b
+
+instance Foldable1 Range
+
+instance Traversable Range where
+    traverse f (Range a b) = Range <$> f a <*> f b
+
+instance Traversable1 Range where
+    traverse1 f (Range a b) = Range <$> f a Data.Functor.Apply.<.> f b
+
+instance D.Distributive Range where
+  collect f x = Range (getL . f <$> x) (getR . f <$> x)
+    where getL (Range l _) = l
+          getR (Range _ r) = r
+
+instance Representable Range where
+  type Rep Range = Bool
+  tabulate f = Range (f False) (f True)
+  index (Range l _) False = l
+  index (Range _ r) True = r
+
+instance (JoinSemiLattice a) => JoinSemiLattice (Range a) where
+  (\/) = liftR2 (\/)
+
+instance (MeetSemiLattice a) => MeetSemiLattice (Range a) where
+  (/\) = liftR2 (/\)
+
+instance (BoundedLattice a) => BoundedJoinSemiLattice (Range a) where
+  bottom = top >.< bottom
+
+instance (BoundedLattice a) => BoundedMeetSemiLattice (Range a) where
+  top = bottom >.< top
+
+instance (Lattice a) => Space (Range a) where
+  type Element (Range a) = a
+
+  lower (Range l _) = l
+  upper (Range _ u) = u
+
+  (>.<) = Range
+
+instance (Lattice a, Field a, Subtractive a, FromInteger a) => FieldSpace (Range a) where
+    type Grid (Range a) = Int
+
+    grid o s n = (+ bool zero (step/(one+one)) (o==MidPos)) <$> posns
+      where
+        posns = (lower s +) . (step *) . fromIntegral <$> [i0..i1]
+        step = (/) (width s) (fromIntegral n)
+        (i0,i1) = case o of
+                    OuterPos -> (zero,n)
+                    InnerPos -> (one,n - one)
+                    LowerPos -> (zero,n - one)
+                    UpperPos -> (one,n)
+                    MidPos -> (zero,n - one)
+    gridSpace r n = zipWith Range ps (drop 1 ps)
+      where
+        ps = grid OuterPos r n
+
+-- | Monoid based on convex hull union
+instance (BoundedLattice a) => Semigroup (Range a) where
+  (<>) a b = getUnion (Union a <> Union b)
+
+instance (BoundedLattice a) => Monoid (Range a) where
+  mempty = getUnion mempty
+
+-- | Numeric algebra based on Interval arithmetic
+-- https://en.wikipedia.org/wiki/Interval_arithmetic
+--
+instance (Additive a, Lattice a) => Additive (Range a) where
+  (Range l u) + (Range l' u') = space1 [l+l',u+u']
+  zero = zero ... zero
+
+instance (Subtractive a, Lattice a) => Subtractive (Range a) where
+  negate (Range l u) = negate u ... negate l
+
+instance (Multiplicative a, Lattice a) => Multiplicative (Range a) where
+  (Range l u) * (Range l' u') =
+    space1 [l * l', l * u', u * l', u * u']
+  one = one ... one
+
+instance (BoundedLattice a, Epsilon a, Divisive a) =>
+  Divisive (Range a)
+  where
+  recip i@(Range l u)
+    | zero |.| i && not (epsilon |.| i) = bottom ... recip l
+    | zero |.| i && not (negate epsilon |.| i) = top ... recip l
+    | zero |.| i = whole
+    | otherwise = recip l ... recip u
+
+instance (Multiplicative a, Subtractive a, Lattice a) => Signed (Range a) where
+    sign (Range l u) = bool (negate one) one (u `joinLeq` l)
+    abs (Range l u) = bool (u ... l) (l ... u) (u `joinLeq` l)
+
+instance (FromInteger a, Lattice a) => FromInteger (Range a) where
+    fromInteger x = fromInteger x ... fromInteger x
+
+type instance Actor (Range a) = a
+
+instance (Additive a) => AdditiveAction (Range a) where
+    (.+) r s = fmap (s+) r
+    (+.) s = fmap (s+)
+instance (Subtractive a) => SubtractiveAction (Range a) where
+    (.-) r s = fmap (\x -> x - s) r
+    (-.) s = fmap (\x -> x - s)
+instance (Multiplicative a) => MultiplicativeAction (Range a) where
+    (.*) r s = fmap (s*) r
+    (*.) s = fmap (s*)
+instance (Divisive a) => DivisiveAction (Range a) where
+    (./) r s = fmap (/ s) r
+    (/.) s = fmap (/ s)
+
+stepSensible :: (Ord a, FromRatio a, FromInteger a, ExpField a, QuotientField a Integer) => Pos -> a -> Integer -> a
+stepSensible tp span n =
+    step + bool zero (step/two) (tp==MidPos)
+  where
+    step' = 10.0 ^^ (floor (logBase 10 (span/fromIntegral n)) :: Integer)
+    err = fromIntegral n / span * step'
+    step
+      | err <= 0.15 = 10.0 * step'
+      | err <= 0.35 = 5.0 * step'
+      | err <= 0.75 = 2.0 * step'
+      | otherwise = step'
+
+gridSensible :: (Ord a, JoinSemiLattice a, FromInteger a, FromRatio a, QuotientField a Integer, ExpField a, Epsilon a) =>
+    Pos -> Bool -> Range a -> Integer -> [a]
+gridSensible tp inside r@(Range l u) n =
+    bool id (filter (`memberOf` r)) inside $
+    (+ bool zero (step/two) (tp==MidPos)) <$> posns
+  where
+    posns = (first' +) . (step *) . fromIntegral <$> [i0..i1]
+    span = u - l
+    step = stepSensible tp span n
+    first' = step * fromIntegral (floor (l/step + epsilon) :: Integer)
+    last' =  step * fromIntegral (ceiling (u/step - epsilon) :: Integer)
+    n' = round ((last' - first')/step)
+    (i0,i1) = case tp of
+                OuterPos -> (0::Integer,n')
+                InnerPos -> (1,n' - 1)
+                LowerPos -> (0,n' - 1)
+                UpperPos -> (1,n')
+                MidPos -> (0,n' - 1)
diff --git a/src/NumHask/Data/RangeD.hs b/src/NumHask/Data/RangeD.hs
new file mode 100644
--- /dev/null
+++ b/src/NumHask/Data/RangeD.hs
@@ -0,0 +1,59 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE ExtendedDefaultRules #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -Wall #-}
+
+-- | representation of a possibly discontinuous interval
+module NumHask.Data.RangeD
+  ( RangeD(..)
+  , normalise
+  ) where
+
+import NumHask.Analysis.Space
+import NumHask.Data.Range
+import NumHask.Algebra.Abstract
+import NumHask.Analysis.Metric
+import Data.Bool (bool)
+import GHC.Generics (Generic)
+import Prelude (Eq(..), Ord(..), Show, Foldable, Functor, Traversable(..), Applicative(..), ($), not, (<$>), Semigroup(..), reverse)
+import Data.List (sortBy, foldl')
+import Data.Ord (comparing)
+
+newtype RangeD a = RangeD [Range a]
+  deriving (Eq, Generic, Show, Functor, Foldable, Traversable)
+
+normalise :: (Ord a, Lattice a, Subtractive a) =>
+    RangeD a -> RangeD a
+normalise (RangeD rs) = RangeD $ reverse $ foldl' step [] (sortBy (comparing lower) rs)
+  where
+    step [] a = [a]
+    step (x:xs) a = (a `unify` x) <> xs
+
+    unify a b = bool (bool [a,b] [b,a] (lower a `joinLeq` lower b)) [a + b] (not $ a `disjoint` b)
+
+instance (Ord a, Lattice a, Subtractive a) => Additive (RangeD a) where
+    (RangeD l0) + (RangeD l1) = normalise $ RangeD $ l0 <> l1
+    zero = RangeD []
+
+instance (Divisive a, Ord a, Lattice a, Subtractive a) => Subtractive (RangeD a) where
+    negate (RangeD rs) = normalise $ RangeD $ negate <$> rs
+
+instance (Ord a, Lattice a, Subtractive a, Multiplicative a) => Multiplicative (RangeD a) where
+    (RangeD a) * (RangeD b) = normalise $ RangeD $ (*) <$> a <*> b
+    one = RangeD [one]
+
+instance (Multiplicative a, BoundedLattice a, Epsilon a, Ord a, Subtractive a, Divisive a) => Divisive (RangeD a) where
+    recip (RangeD rs) = normalise $ RangeD $ recip <$> rs
+
diff --git a/src/NumHask/Data/Rect.hs b/src/NumHask/Data/Rect.hs
new file mode 100644
--- /dev/null
+++ b/src/NumHask/Data/Rect.hs
@@ -0,0 +1,194 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# OPTIONS_GHC -Wall #-}
+
+-- | a two-dimensional plane, implemented as a composite of a 'Pair' of 'Range's.
+module NumHask.Data.Rect
+  ( Rect(..)
+  , pattern Rect
+  , pattern Ranges
+  , corners
+  , projectRect
+  ) where
+
+import Data.Bool (bool)
+import GHC.Exts
+import GHC.Generics (Generic)
+import Data.Distributive
+import Data.Functor.Compose
+import Data.Functor.Rep
+import NumHask.Data.Pair
+import Prelude (Eq(..), Show(..), Bool(..), Foldable(..), Functor, Traversable(..), Applicative, (.), fmap, (<$>), Semigroup(..), Monoid(..))
+import NumHask.Data.Range
+import NumHask.Data.Integral
+import NumHask.Analysis.Space
+import NumHask.Algebra.Abstract
+
+-- $setup
+-- >>> :set -XNoImplicitPrelude
+
+-- | a 'Pair' of 'Ranges' that form a rectangle in what is often thought of as the XY plane.
+--
+-- >>> let a = Rect (-1) 1 (-2) 4
+-- >>> a
+-- Rect -1 1 -2 4
+-- >>> let (Ranges x y) = a
+-- >>> x
+-- Range -1 1
+-- >>> y
+-- Range -2 4
+-- >>> fmap (+1) (Rect 1 2 3 4)
+-- Rect 2 3 4 5
+-- >>> one :: Rect Double
+-- Rect -0.5 0.5 -0.5 0.5
+-- >>> zero :: Rect Double
+-- Rect Infinity -Infinity Infinity -Infinity
+--
+-- as a Field instance
+--
+-- >>> Rect 0 1 2 3 + zero
+-- Rect 0.0 1.0 2.0 3.0
+-- >>> Rect 0 1 (-2) (-1) + Rect 2 3 (-5) 3
+-- Rect 0.0 3.0 -5.0 3.0
+-- >>> Rect 1 1 1 1 - one
+-- Rect 0.5 1.0 0.5 1.0
+-- >>> Rect 0 1 0 1 * one
+-- Rect 0.0 1.0 0.0 1.0
+-- >>> Rect 0 1 0 1 / one
+-- Rect 0.0 1.0 0.0 1.0
+-- >>> singleton (Pair 1.0 2.0) :: Rect Double
+-- Rect 1.0 1.0 2.0 2.0
+-- >>> abs (Rect 1 0 1 0)
+-- Rect 0.0 1.0 0.0 1.0
+-- >>> sign (Rect 1 0 1 0) == negate one
+-- True
+--
+-- as a Space instance
+--
+-- >>> project (Rect 0 1 (-1) 0) (Rect 1 4 10 0) (Pair 0.5 1)
+-- Pair 2.5 -10.0
+-- >>> gridSpace (Rect 0 10 0 1) (Pair 2 2)
+-- [Rect 0.0 5.0 0.0 0.5,Rect 0.0 5.0 0.5 1.0,Rect 5.0 10.0 0.0 0.5,Rect 5.0 10.0 0.5 1.0]
+-- >>> grid MidPos (Rect 0 10 0 1) (Pair 2 2)
+-- [Pair 2.5 0.25,Pair 2.5 0.75,Pair 7.5 0.25,Pair 7.5 0.75]
+newtype Rect a =
+  Rect' (Compose Pair Range a)
+  deriving (Eq, Functor, Applicative, Foldable, Traversable,
+            Generic)
+
+-- | pattern of Rect lowerx upperx lowery uppery
+pattern Rect :: a -> a -> a -> a -> Rect a
+pattern Rect a b c d = Rect' (Compose (Pair (Range a b) (Range c d)))
+{-# COMPLETE Rect#-}
+
+-- | pattern of Ranges xrange yrange
+pattern Ranges :: Range a -> Range a -> Rect a
+pattern Ranges a b = Rect' (Compose (Pair a b))
+{-# COMPLETE Ranges#-}
+
+instance (Show a) => Show (Rect a) where
+  show (Rect a b c d) =
+    "Rect " <> show a <> " " <> show b <> " " <> show c <> " " <> show d
+
+instance Data.Distributive.Distributive Rect where
+  collect f x =
+    Rect (getA . f <$> x) (getB . f <$> x) (getC . f <$> x) (getD . f <$> x)
+    where
+      getA (Rect a _ _ _) = a
+      getB (Rect _ b _ _) = b
+      getC (Rect _ _ c _) = c
+      getD (Rect _ _ _ d) = d
+ 
+instance Representable Rect where
+  type Rep Rect = (Bool, Bool)
+  tabulate f =
+    Rect (f (False, False)) (f (False, True)) (f (True, False)) (f (True, True))
+  index (Rect a _ _ _) (False, False) = a
+  index (Rect _ b _ _) (False, True) = b
+  index (Rect _ _ c _) (True, False) = c
+  index (Rect _ _ _ d) (True, True) = d
+
+instance (BoundedLattice a) => Semigroup (Rect a) where
+  (<>) (Ranges x y) (Ranges x' y') = Ranges (x `union` x') (y `union` y')
+
+instance (BoundedLattice a) => Monoid (Rect a) where
+  mempty = Ranges mempty mempty
+
+instance (Lattice a) => Space (Rect a) where
+  type Element (Rect a) = Pair a
+
+  union (Ranges a b) (Ranges c d) = Ranges (a `union` c) (b `union` d)
+  intersection (Ranges a b) (Ranges c d) = Ranges (a `intersection` c) (b `intersection` d)
+
+  (>.<) (Pair l0 l1) (Pair u0 u1) = Rect l0 u0 l1 u1
+
+  lower (Rect l0 _ l1 _) = Pair l0 l1
+  upper (Rect _ u0 _ u1) = Pair u0 u1
+
+  singleton (Pair x y) = Rect x x y y
+
+instance (Lattice a, Field a, Subtractive a, FromInteger a) => FieldSpace (Rect a) where
+    type Grid (Rect a) = Pair Int
+
+    grid o s n = (+ bool zero (step/(one+one)) (o==MidPos)) <$> posns
+      where
+      posns =
+        (lower s +) . (step *) . fmap fromIntegral <$>
+        [Pair x y | x <- [x0 .. x1], y <- [y0 .. y1]]
+      step = (/) (width s) (fromIntegral <$> n)
+      (Pair x0 y0, Pair x1 y1) =
+        case o of
+          OuterPos -> (zero, n)
+          InnerPos -> (one, n - one)
+          LowerPos -> (zero, n - one)
+          UpperPos -> (one, n)
+          MidPos -> (zero, n - one)
+
+    gridSpace (Ranges rX rY) (Pair stepX stepY) =
+      [ Rect x (x + sx) y (y + sy)
+      | x <- grid LowerPos rX stepX
+      , y <- grid LowerPos rY stepY
+      ]
+      where
+        sx = width rX / fromIntegral stepX
+        sy = width rY / fromIntegral stepY
+
+-- | create a list of pairs representing the lower left and upper right cormners of a rectangle.
+corners :: (Lattice a) => Rect a -> [Pair a]
+corners r = [lower r, upper r]
+
+-- | project a Rect from an old range to a new one
+projectRect ::
+     (Lattice a, Subtractive a, Field a)
+  => Rect a
+  -> Rect a
+  -> Rect a
+  -> Rect a
+projectRect r0 r1 (Rect a b c d) = Rect a' b' c' d'
+  where
+    (Pair a' c') = project r0 r1 (Pair a c)
+    (Pair b' d') = project r0 r1 (Pair b d)
+
+type instance Actor (Rect a) = a
+
+instance (Additive a) => AdditiveAction (Rect a) where
+    (.+) r s = fmap (s+) r
+    (+.) s = fmap (s+)
+instance (Subtractive a) => SubtractiveAction (Rect a) where
+    (.-) r s = fmap (\x -> x - s) r
+    (-.) s = fmap (\x -> x - s)
+instance (Multiplicative a) => MultiplicativeAction (Rect a) where
+    (.*) r s = fmap (s*) r
+    (*.) s = fmap (s*)
+instance (Divisive a) => DivisiveAction (Rect a) where
+    (./) r s = fmap (/ s) r
+    (/.) s = fmap (/ s)
