diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2008, Bertram Felgenhauer
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. 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.
+3. Neither the name of the author nor the names of his 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 b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,32 @@
+What is this?
+-------------
+
+ivar-simple provides immutable, write-once variables (IVars) in the
+Data.IVar.Simple module.
+
+It also provides two more experimental channel implementations built on
+top of IVars,
+
+    Data.IVar.Simple.IChan:
+        multi-cast channels with write-once semantics.
+
+    Data.IVar.Simple.MIChan:
+        channels with write semantics similar to Control.Concurrent.Chan
+
+Comparison to data-ivar
+-----------------------
+
+Both data-ivar and ivar-simple provide a write-once variable. That's where
+the similarities end:
+
+- Reading an IVar with data-ivar is an IO operation. In ivar-simple it's a
+  pure function.
+- data-ivar provides a 'Reader' monoid, monad, etc. for reading from one of
+  several IVars. ivar-simple has no such functionality.
+- The data-ivar implementation can, in principle, add arbitrary IO actions
+  that are called when an IVar is written. (This detail is not exposed,
+  however)
+- Technically, ivar-simple tries for efficiency by exploiting the existing
+  locking structures in the RTS; in particular, reading a full IVar for a
+  second time is as cheap as evaluating a record selector.
+  I have no performance numbers for this.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,3 @@
+#! /usr/bin/env runghc
+> import Distribution.Simple (defaultMain)
+> main = defaultMain
diff --git a/ivar-simple.cabal b/ivar-simple.cabal
new file mode 100644
--- /dev/null
+++ b/ivar-simple.cabal
@@ -0,0 +1,29 @@
+Name:          ivar-simple
+Version:       0.1.0.0
+Category:      Concurrency
+Stability:     experimental
+Copyright:     (c) 2008, 2009 Bertram Felgenhauer
+Maintainer:    Bertram Felgenhauer <int-e@gmx.de>
+License:       MIT
+License-File:  LICENSE
+Synopsis:      Write once concurrency primitives.
+Description:
+  @IVar@s are write-once variables.
+  .
+  They can be read, an operation that will block until a value was written
+  to the variable. They can be written to exactly once.
+Cabal-Version: >= 1.4
+Build-Type:    Simple
+Extra-Source-Files:
+    README
+
+Library
+    HS-Source-Dirs:     src
+    Exposed-Modules:
+        Data.IVar.Simple
+        Data.IVar.Simple.IChan
+        Data.IVar.Simple.MIChan
+    Build-Depends:      base
+    -- workaround for http://hackage.haskell.org/trac/ghc/ticket/2756
+    if impl(ghc <= 6.10.1)
+        GHC-Options:        -fno-state-hack
diff --git a/src/Data/IVar/Simple.hs b/src/Data/IVar/Simple.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/IVar/Simple.hs
@@ -0,0 +1,82 @@
+-- |
+-- Module      : Data.IVar.Simple
+-- Copyright   : (c) 2008, 2009 Bertram Felgenhauer
+-- License     : BSD3
+--
+-- Maintainer  : Bertram Felgenhauer <int-e@gmx.de>
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- 'IVar's are write-once variables.
+--
+-- Similarily to 'MVar's, 'IVar's can be either empty or filled. Once filled,
+-- they keep their value indefinitely - they are immutable.
+--
+-- Reading from an empty 'IVar' will block until the 'IVar' is filled. Because
+-- the value read will never change, this is a pure computation.
+--
+module Data.IVar.Simple (
+    IVar,
+    new,
+    newFull,
+    read,
+    tryRead,
+    write,
+    tryWrite,
+) where
+
+import Control.Concurrent.MVar
+import Control.Exception
+import Control.Monad
+import System.IO.Unsafe
+import Prelude hiding (read)
+
+-- | A write-once (/immutable/) Variable
+data IVar a = IVar (MVar ()) (MVar a) a
+
+-- | Creates a new, empty 'IVar'.
+new :: IO (IVar a)
+new = do
+    lock <- newMVar ()
+    trans <- newEmptyMVar
+    let {-# NOINLINE value #-}
+        value = unsafePerformIO $ takeMVar trans
+    return (IVar lock trans value)
+
+-- | Create a new filled 'IVar'.
+--
+-- This is slightly cheaper than creating a new 'IVar' and then writing to it.
+newFull :: a -> IO (IVar a)
+newFull value = do
+    lock <- newEmptyMVar
+    return (IVar lock (error "unused MVar") value)
+
+-- | Returns the value of an 'IVar'.
+--
+-- The evaluation will block until a value is written to the 'IVar' if there
+-- is no value yet.
+read :: IVar a -> a
+read (IVar _ _ value) = value
+
+-- | Try to read an 'IVar'. Returns 'Nothing' if there is not value yet.
+tryRead :: IVar a -> IO (Maybe a)
+tryRead (IVar lock _ value) = do
+    empty <- isEmptyMVar lock
+    if empty then return (Just value) else return Nothing
+
+-- | Writes a value to an 'IVar'. Raises a 'BlockedIndefinitely' exception if
+-- it fails.
+write :: IVar a -> a -> IO ()
+write ivar value = do
+    result <- tryWrite ivar value
+    when (not result) $ throwIO BlockedIndefinitely
+-- Note: It would be easier to block forever when the IVar is full. However,
+-- the thread would likely not be garbage collected then.
+
+-- | Writes a value to an 'IVar'. Returns 'True' if successful.
+tryWrite :: IVar a -> a -> IO Bool
+tryWrite (IVar lock trans _) value = do
+    a <- tryTakeMVar lock
+    case a of
+        Just _  -> putMVar trans value >> return True
+        Nothing -> return False
diff --git a/src/Data/IVar/Simple/IChan.hs b/src/Data/IVar/Simple/IChan.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/IVar/Simple/IChan.hs
@@ -0,0 +1,65 @@
+-- |
+-- Module      : Data.IVar.Simple.IChan
+-- Copyright   : (c) 2008, 2009 Bertram Felgenhauer
+-- License     : BSD3
+--
+-- Maintainer  : Bertram Felgenhauer <int-e@gmx.de>
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- An 'IChan's is a type of multicast channel built on top of 'IVar.IVar's.
+-- It supports multiple readers. The 'IChan' data type represents the head
+-- of a channel.
+--
+-- Writing to an 'IChan' head has write-once semantics similar to 'IVar.IVar's:
+-- only the first of several attempts to write to the head will succeed,
+-- returning a new 'IChan' head for writing more values.
+--
+module Data.IVar.Simple.IChan (
+    IChan,
+    new,
+    read,
+    write,
+    tryWrite,
+) where
+
+import Prelude hiding (read)
+import qualified Data.IVar.Simple as IVar
+import Control.Monad
+import Control.Concurrent.MVar
+
+-- | A channel head
+newtype IChan a = IChan (IVar.IVar (a, IChan a))
+
+-- | Create a new channel.
+new :: IO (IChan a)
+new = IChan `fmap` IVar.new
+
+-- | Returns the contents of a channel as a list, starting at the channel
+-- head.
+--
+-- This is a pure computation. Forcing elements of the list may, however,
+-- block.
+read :: IChan a -> [a]
+read (IChan as) = let (a, ic) = IVar.read as in a : read ic
+
+-- | Write a single value to the channel.
+--
+-- Raises a 'BlockedIndefinitely' exception if a value has already been
+-- written to the channel. Otherwise, returns a new channel head for
+-- writing further values.
+write :: IChan a -> a -> IO (IChan a)
+write (IChan as) a = do
+    ic <- new
+    IVar.write as (a, ic)
+    return ic
+
+-- | Attempts to write a single value to the channel.
+--
+-- If a value has already been written, returns 'Nothing'. Otherwise,
+-- returns a new channel head for writing further values.
+tryWrite :: IChan a -> a -> IO (Maybe (IChan a))
+tryWrite (IChan as) a = do
+    ic <- new
+    success <- IVar.tryWrite as (a, ic)
+    return (guard success >> return ic)
diff --git a/src/Data/IVar/Simple/MIChan.hs b/src/Data/IVar/Simple/MIChan.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/IVar/Simple/MIChan.hs
@@ -0,0 +1,68 @@
+-- |
+-- Module      : Data.IVar.Simple.MIChan
+-- Copyright   : (c) 2008, 2009 Bertram Felgenhauer
+-- License     : BSD3
+--
+-- Maintainer  : Bertram Felgenhauer <int-e@gmx.de>
+-- Stability   : experimental
+-- Portability : ghc
+--
+-- An 'MIChan' is a multicast channel built on top of an 'IChan.IChan'.
+--
+-- Like 'IChan.IChan', this supports multiple readers. It is comparable to
+-- a @Control.Concurrent.Chan.Chan@ for the writing end: Each write will
+-- append an element to the channel. No writes will fail.
+
+module Data.IVar.Simple.MIChan (
+    -- $comparison
+    MIChan,
+    new,
+    read,
+    write,
+    writeList,
+) where
+
+import Prelude hiding (read)
+import qualified Data.IVar.Simple.IChan as IChan
+import Control.Monad
+import Control.Concurrent.MVar
+
+-- | A multicast channel.
+newtype MIChan a = MIChan (MVar (IChan.IChan a))
+
+-- | Create a new multicast channel.
+new :: IO (MIChan a)
+new = do
+    ic <- IChan.new
+    MIChan `fmap` newMVar ic
+
+-- | Return the list of values that the channel represents.
+read :: MIChan a -> IO [a]
+read (MIChan mic) = do
+    IChan.read `fmap` readMVar mic
+
+-- | Send a value across the channel.
+write :: MIChan a -> a -> IO ()
+write (MIChan mic) value = do
+    modifyMVar_ mic (\ic -> IChan.write ic value)
+
+-- | Send several values across the channel, atomically.
+writeList :: MIChan a -> [a] -> IO ()
+writeList (MIChan mic) values = do
+    modifyMVar_ mic (\ic -> foldM IChan.write ic values)
+
+-- $comparison
+-- Comparison to @Control.Concurrent.Chan@:
+--
+-- > Control.Concurrent.Chan.Chan => Data.MIChan
+-- > newChan                      => new
+-- > writeChan                    => write
+-- > getChanContents              => read
+-- > writeList2Chan               => writeList
+--
+-- These can't be implemented:
+--
+-- > readChan     (can't steal items from other readers)
+-- > unGetChan    (there is no separate reading end)
+-- > isEmptyChan  (needs an IO interface for reading)
+-- > dupChan      (not needed)
