diff --git a/Control-Monad-ST2.cabal b/Control-Monad-ST2.cabal
new file mode 100644
--- /dev/null
+++ b/Control-Monad-ST2.cabal
@@ -0,0 +1,54 @@
+-- Copyright 2013 Kevin Backhouse.
+
+name:                Control-Monad-ST2
+version:             0.1.0.1
+synopsis:            A variation on the ST monad with two type parameters.
+description:         The ST2 monad is like the ST monad, but with
+                     finer-grained control over access to mutable
+                     state. The phantom type parameters r and w are
+                     used to track the read and write dependencies of
+                     the computation. If a computation of type ST2 r w a
+                     is polymorphic in w then it does not write any
+                     external state. If it is also polymorphic in r
+                     then it does not read any external state.
+
+homepage:            https://github.com/kevinbackhouse/Control-Monad-ST2
+license:             BSD3
+license-file:        LICENSE
+author:              Kevin Backhouse
+maintainer:          Kevin.Backhouse@gmail.com
+copyright:           Kevin Backhouse, 2013
+category:            Control
+build-type:          Simple
+cabal-version:       >=1.8
+Extra-source-files:  README.txt
+tested-with:         GHC==7.6.2
+
+source-repository this
+  type:      git
+  location:  https://github.com/kevinbackhouse/Control-Monad-ST2.git
+  tag:       Version-0.1.0.1
+
+library
+  exposed-modules:   Control.Monad.ST2
+  hs-source-dirs:    src
+  build-depends:     base >= 4.5 && < 5,
+                     array, SafeSemaphore, QuickCheck
+  ghc-options:       -Wall
+  extensions:        Safe RankNTypes ExistentialQuantification
+                     DeriveFunctor
+
+test-suite Main
+  type:              exitcode-stdio-1.0
+  x-uses-tf:         true
+  build-depends:     base >= 4.5 && < 5,
+                     array, SafeSemaphore, QuickCheck,
+                     mtl,
+                     test-framework,
+                     test-framework-quickcheck2,
+                     test-framework-hunit
+  ghc-options:       -Wall
+  extensions:        RankNTypes ExistentialQuantification
+                     DeriveFunctor
+  hs-source-dirs:    src, src/Control, src/Control/Monad, tests
+  main-is:           Main.hs
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Kevin Backhouse
+
+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 Kevin Backhouse 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/README.txt b/README.txt
new file mode 100644
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,14 @@
+Control.Monad.ST2
+=================
+
+The Control.Monad.ST2 monad is like the Control.Monad.ST monad, but
+with finer-grained control over access to mutable state. The phantom
+type parameters r and w are used to track the read and write
+dependencies of the computation. If a computation of type ST2 r w a is
+polymorphic in w then it does not write any external state. If it is
+also polymorphic in r then it does not read any external state.
+
+Change log
+----------
+
+0.1.0.0 - First version
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,4 @@
+-- Copyright 2013 Kevin Backhouse.
+
+import Distribution.Simple
+main = defaultMain
diff --git a/src/Control/Monad/ST2.hs b/src/Control/Monad/ST2.hs
new file mode 100644
--- /dev/null
+++ b/src/Control/Monad/ST2.hs
@@ -0,0 +1,273 @@
+-- Copyright 2013 Kevin Backhouse.
+
+{-|
+  The 'ST2' monad is like the 'Control.Monad.ST.ST' monad, but with
+  finer-grained control over access to mutable state. The phantom type
+  parameters @r@ and @w@ are used to track the read and write
+  dependencies of the computation. If a computation of type @'ST2' r w
+  a@ is polymorphic in w then it does not write any external state. If
+  it is also polymorphic in @r@ then it does not read any external
+  state.  Operations which modify state, such as 'writeST2Ref', are
+  considered to read external state as well as write it, so it is
+  impossible for a computation of type @'ST2' r w a@ to be polymorphic
+  in @r@ but not in @w@. This means that the @r@ type parameter
+  behaves exactly like the @s@ type parameter on the
+  'Control.Monad.ST.ST' monad, but the @w@ type parameter provides
+  extra information which is not available in the 'Control.Monad.ST.ST'
+  monad.
+
+  Like the 'Control.Monad.ST.ST' monad, the 'ST2' monad allows mutable
+  references and arrays to be created, read, and written within a
+  computation.  Provided that the mutable state does not escape, it
+  does not affect the type signature of the top-level computation.
+-}
+
+module Control.Monad.ST2
+  ( -- * ST2 Monad
+    ST2
+  , pureST2, PureST2(..)
+  , readOnlyST2, ReadOnlyST2(..)
+  , ioToST2, st2ToIO
+
+    -- * ST2 References
+  , ST2Ref
+  , newST2Ref, readST2Ref, writeST2Ref, modifyST2Ref
+  , importST2Ref, exportST2Ref
+
+    -- * ST2 Arrays
+  , ST2Array
+  , newST2Array, newST2Array_
+  , readST2Array, writeST2Array
+  , boundsST2Array
+  , importST2Array, exportST2Array
+
+    -- * ST2 Read-only Arrays
+  , ST2RArray
+  , mkST2RArray
+  , readST2RArray
+
+    -- * Parallelism
+  , parallelST2
+  )
+where
+
+import Control.Exception ( assert, bracket_ )
+import Control.Concurrent ( forkIO )
+import qualified Control.Concurrent.SSem as S
+import Data.Array.IO.Safe
+import Data.IORef
+
+-- | The 'ST2' monad is a newtype of 'IO'. The type parameters @r@ and
+-- @w@ are phantom type parameters which are used to track the read
+-- and write dependencies of the computation.
+newtype ST2 r w a
+  = ST2 { unwrapST2 :: IO a }
+    deriving Functor
+
+instance Monad (ST2 r w) where
+  return x = ST2 $ return x
+
+  (ST2 m) >>= f =
+    ST2 $
+    do x <- m
+       unwrapST2 (f x)
+
+  fail msg = ST2 $ fail msg
+
+-- | This function checks that the sub-computation is polymorphic in
+-- both type parameters. This means that the sub-computation does not
+-- read or write any state from the enclosing context.
+pureST2 :: (forall r w. ST2 r w a) -> ST2 r' w' a
+pureST2 m = m
+
+-- | This function checks that the sub-computation is polymorphic in
+-- the @w@ type parameter. This means that the sub-computation does
+-- not write any state from the enclosing context (but read-only
+-- operations are permitted).
+readOnlyST2 :: (forall w. ST2 r w a) -> ST2 r w' a
+readOnlyST2 m = m
+
+-- | 'runPureST2' is semantically equivalent to pureST2, but uses a
+-- newtype to package the @forall@. Sometimes this packaging is
+-- convenient when passing a value of type 'PureST2' as an argument
+-- because it avoids the need for a nested @forall@ in the type
+-- signature.
+newtype PureST2 a
+  = PureST2 { runPureST2 :: forall r w. ST2 r w a }
+
+-- | 'runReadOnlyST2' is semantically equivalent to readOnlyST2, but
+-- uses a newtype to package the @forall@. Sometimes this packaging is
+-- convenient when passing a value of type 'ReadOnlyST2' as an
+-- argument because it avoids the need for a nested @forall@ in the
+-- type signature.
+newtype ReadOnlyST2 r a
+  = ReadOnlyST2 { runReadOnlyST2 :: forall w. ST2 r w a }
+
+-- | 'IO' computations can be converted to 'ST2' computations, but
+-- only with a monomorphic type signature.
+ioToST2 :: IO a -> ST2 () () a
+ioToST2 m = ST2 m
+
+-- | 'ST2' computations can be converted to 'IO' computations, but
+-- only with a monomorphic type signature.
+st2ToIO :: ST2 () () a -> IO a
+st2ToIO (ST2 m) = m
+
+-- | Mutable reference. 'ST2Ref' is actually just a newtype of an
+-- 'IORef', but the @r@ and @w@ type parameters allow the read and
+-- write dependencies to be tracked by the type system.
+newtype ST2Ref r w a
+  = ST2Ref (IORef a)
+
+-- | Create a new reference. The @r@ and @w@ type parameters of the
+-- reference are unified with the 'ST2' monad to indicate that new
+-- state is created in the enclosing context.
+{-# INLINE newST2Ref #-}
+newST2Ref :: a -> ST2 r w (ST2Ref r w a)
+newST2Ref x =
+  ST2 $
+  do r <- newIORef x
+     return (ST2Ref r)
+
+-- | Read a reference. The @w@ type parameter of the reference is not
+-- unified with the 'ST2' monad to indicate that this access is
+-- read-only.
+{-# INLINE readST2Ref #-}
+readST2Ref :: ST2Ref r w a -> ST2 r w' a
+readST2Ref (ST2Ref r) =
+  ST2 $ readIORef r
+
+-- | Write to a reference. The @w@ type parameter of the reference is
+-- unified with the 'ST2' monad to indicate that state is written in
+-- the enclosing context.
+{-# INLINE writeST2Ref #-}
+writeST2Ref :: ST2Ref r w a -> a -> ST2 r w ()
+writeST2Ref (ST2Ref r) x =
+  ST2 $ writeIORef r x
+
+-- | Modify a reference.
+{-# INLINE modifyST2Ref #-}
+modifyST2Ref :: ST2Ref r w a -> (a -> a) -> ST2 r w ()
+modifyST2Ref (ST2Ref r) f =
+  ST2 $ modifyIORef r f
+
+-- | Convert an IORef to an ST2Ref.
+{-# INLINE importST2Ref #-}
+importST2Ref :: IORef a -> ST2Ref () () a
+importST2Ref r = ST2Ref r
+
+-- | Convert an ST2Ref to an IORef.
+{-# INLINE exportST2Ref #-}
+exportST2Ref :: ST2Ref () () a -> IORef a
+exportST2Ref (ST2Ref r) = r
+
+-- | Mutable array. 'ST2Array' is actually just a newtype of an
+-- 'IOArray', but the @r@ and @w@ type parameters allow the read and
+-- write dependencies to be tracked by the type system.
+newtype ST2Array r w i a
+  = ST2Array (IOArray i a)
+
+-- | Create an array with an initial value.
+{-# INLINE newST2Array #-}
+newST2Array :: Ix i => (i,i) -> a -> ST2 r w (ST2Array r w i a)
+newST2Array bnds x =
+  ST2 $
+  do xs <- newArray bnds x
+     return (ST2Array xs)
+
+-- | Create an uninitialised array.
+{-# INLINE newST2Array_ #-}
+newST2Array_ :: Ix i => (i,i) -> ST2 r w (ST2Array r w i a)
+newST2Array_ bnds =
+  ST2 $
+  do xs <- newArray_ bnds
+     return (ST2Array xs)
+
+-- | Read an index of the array. The @w@ type parameter of the
+-- reference is not unified with the 'ST2' monad to indicate that this
+-- access is read-only.
+{-# INLINE readST2Array #-}
+readST2Array :: Ix i => ST2Array r w i a -> i -> ST2 r w' a
+readST2Array (ST2Array xs) i =
+  ST2 $ readArray xs i
+
+-- | Write an index of the array. The @w@ type parameter of the array
+-- is unified with the 'ST2' monad to indicate that state is written
+-- in the enclosing context.
+{-# INLINE writeST2Array #-}
+writeST2Array :: Ix i => ST2Array r w i a -> i -> a -> ST2 r w ()
+writeST2Array (ST2Array xs) i a =
+  ST2 $ writeArray xs i a
+
+-- | Read the size of the array. Neither type parameter is unified with
+-- the 'ST2' monad because the array itself is not accessed.
+-- (Conceptually, an 'ST2Array' is a pair, consisting of the size
+-- information and the array. This function only accesses the size
+-- information, which is immutable.)
+{-# INLINE boundsST2Array #-}
+boundsST2Array :: Ix i => ST2Array r w i a -> ST2 r' w' (i,i)
+boundsST2Array (ST2Array xs) =
+  ST2 $ getBounds xs
+
+-- | Convert an IOArray to an ST2Array.
+{-# INLINE importST2Array #-}
+importST2Array :: IOArray i a -> ST2Array () () i a
+importST2Array r = ST2Array r
+
+-- | Convert an ST2Array to an IOArray.
+{-# INLINE exportST2Array #-}
+exportST2Array :: ST2Array () () i a -> IOArray i a
+exportST2Array (ST2Array r) = r
+
+-- | Read-only array. Existential quantification is used to hide the
+-- @w@ type parameter. This means that it can escape a 'ReadOnlyST2'
+-- context, and can be read in the enclosing context. However, it is
+-- impossible for anyone to write to the array outside of the
+-- 'ReadOnlyST2' context in which the array was created.
+data ST2RArray r i a
+  = forall w. ST2RArray !(ST2Array r w i a)
+
+-- | Create a read-only array. It is important to note that this
+-- function does not make the original 'ST2Array' immutable. It merely
+-- creates a read-only reference to the original array. However, the
+-- 'ST2RArray' can be returned through a 'readOnlyST2' or
+-- 'ReadOnlyST2' context and the typing rules ensure that the original
+-- 'ST2Array' cannot be modified outside of the 'ReadOnlyST2' context
+-- in which it was created. In other words, the original 'ST2Array'
+-- can continue to be modified after the 'ST2RArray' is created, but
+-- only until the 'ST2RArray' is returned through a 'ReadOnlyST2'
+-- context.
+{-# INLINE mkST2RArray #-}
+mkST2RArray :: ST2Array r w i a -> ST2RArray r i a
+mkST2RArray xs =
+  ST2RArray xs
+
+-- | Read an index of the read-only array.
+{-# INLINE readST2RArray #-}
+readST2RArray :: Ix i => ST2RArray r i a -> i -> ST2 r w a
+readST2RArray (ST2RArray xs) i =
+  readST2Array xs i
+
+
+----------------------------------------------------------------------
+------------------------------ Threads -------------------------------
+----------------------------------------------------------------------
+
+-- | Spawn one thread for each index in the range and wait for all the
+-- threads to finish. Each thread is parameterised by its index, which
+-- is an element of the range.
+parallelST2 :: Ix i => (i,i) -> (i -> ST2 r w ()) -> ST2 r w ()
+parallelST2 bnds f =
+  let n = rangeSize bnds in
+  assert (n > 0) $
+  ST2 $
+  -- If n == 1 then there is no need to use forkIO.
+  if n == 1
+     then unwrapST2 (f (fst bnds))
+     else do s <- S.new 0
+             sequence_
+               [ forkIO $
+                 bracket_ (return ()) (S.signal s) (unwrapST2 (f i))
+               | i <- range bnds
+               ]
+             S.waitN s n
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,113 @@
+-- Copyright 2013 Kevin Backhouse.
+
+module Main where
+
+import Control.Monad.ST2
+import Control.Monad.State.Strict
+import Control.Monad.Writer.Strict
+import Data.Bits
+
+import Test.Framework as TF ( defaultMain, testGroup, Test )
+import Test.Framework.Providers.QuickCheck2 ( testProperty )
+import Test.QuickCheck as QC
+import Test.QuickCheck.Property ( morallyDubiousIOProperty )
+
+-- | 'TestST2' is a trivial wrapper around 'PureST2'. Its only purpose
+-- is to define a 'QC.Testable' instance for 'PureST2'.
+newtype TestST2 a
+  = TestST2 (PureST2 a)
+
+instance QC.Testable a => QC.Testable (TestST2 a) where
+  property (TestST2 (PureST2 m)) = morallyDubiousIOProperty (st2ToIO m)
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: [TF.Test]
+tests =
+  [ testGroup "Control.Monad.ST2"
+      [ testProperty "CreateWriteRead" prop_CreateWriteRead
+      , testProperty "ST2RArray" prop_ST2RArray
+      , testProperty "parallelST2" prop_parallelST2
+      ]
+  ]
+
+-- | This property creates an array, initialises its elements, then
+-- reads the elements to check that it worked correctly.
+prop_CreateWriteRead :: Int -> TestST2 Bool
+prop_CreateWriteRead n0 =
+  let n = n0 `mod` 0x100 in
+  let ks = [0 .. n-1] in
+  TestST2 $ PureST2 $
+  do xs <- newST2Array_ (0,n-1)
+     sequence_
+       [ writeST2Array xs k k
+       | k <- ks
+       ]
+     ks' <- mapM (readST2Array xs) ks
+     return (ks == ks')
+
+-- | This property is a demonstration of how a read-only array can be
+-- returned through 'readOnlyST2'. The type checker rejects this code if
+-- prop_ST2RArray_helper returns an 'ST2Array' rather than an
+-- 'ST2RArray'.
+prop_ST2RArray :: Int -> TestST2 Bool
+prop_ST2RArray n0 =
+  let n = n0 `mod` 0x100 in
+  let ks = [0 .. n-1] in
+  TestST2 $ PureST2 $
+  do xs <- readOnlyST2 (prop_ST2RArray_helper n)
+     ks' <- mapM (readST2RArray xs) ks
+     return (ks == ks')
+
+prop_ST2RArray_helper :: Int -> ST2 r w (ST2RArray r Int Int)
+prop_ST2RArray_helper n =
+   do xs <- newST2Array_ (0,n-1)
+      sequence_
+        [ writeST2Array xs i i
+        | i <- [0 .. n-1]
+        ]
+      return (mkST2RArray xs)
+
+prop_parallelST2 :: Int -> TestST2 Bool
+prop_parallelST2 k0 =
+  let nShift = 12 in
+  let k = k0 `mod` (nShift+1) in
+  let n = (1 :: Int) `shiftL` nShift in
+  TestST2 $ PureST2 $
+  do let bnds = (0,n-1)
+     xs <- newST2Array_ bnds
+     let n' = n `shiftR` k
+     let bnds' = (0,n'-1)
+
+     -- Initialise the array, using multiple threads.
+     parallelST2 bnds' $ \i ->
+       sequence_
+         [ let j' = (i `shiftL` k) .|. j in
+           writeST2Array xs j' j'
+         | j <- [0 .. (1 `shiftL` k) - 1]
+         ]
+
+     -- Compute sub-totals, using multiple threads.
+     ys <- newST2Array_ bnds'
+     parallelST2 bnds' $ \i ->
+       do Sum total <-
+            execWriterT $
+            sequence_
+              [ do x <- lift $ readST2Array xs ((i `shiftL` k) .|. j)
+                   tell (Sum x)
+              | j <- [0 .. (1 `shiftL` k) - 1]
+              ]
+          writeST2Array ys i total
+
+     -- Sum the sub-totals.
+     Sum total <-
+       execWriterT $
+       sequence_
+         [ do y <- lift $ readST2Array ys i
+              tell (Sum y)
+         | i <- [0 .. n' - 1]
+         ]
+
+     -- Check the answer.
+     return (total == (n * (n-1)) `div` 2)
