diff --git a/Data/Conduit/Binary.hs b/Data/Conduit/Binary.hs
--- a/Data/Conduit/Binary.hs
+++ b/Data/Conduit/Binary.hs
@@ -297,23 +297,21 @@
 --
 -- Since 0.3.0
 lines :: Monad m => Conduit S.ByteString m S.ByteString
-lines =
-    conduitState id push close
+lines = NeedInput (push id) (close S.empty)
   where
-    push front bs' = return $ StateProducing leftover ls
+    push :: Monad m => (S.ByteString -> S.ByteString)
+                    -> S.ByteString
+                    -> Conduit S.ByteString m S.ByteString
+    push sofar more =
+        case S.uncons second of
+            Just (_, second') -> HaveOutput (push id second') (return ()) (sofar first)
+            Nothing ->
+                let rest = sofar more
+                 in NeedInput (push $ S.append rest) (close rest)
       where
-        bs = front bs'
-        (leftover, ls) = getLines id bs
+        (first, second) = S.breakByte 10 more
 
-    getLines front bs
-        | S.null bs = (id, front [])
-        | S.null y = (S.append x, front [])
-        | otherwise = getLines (front . (x:)) (S.drop 1 y)
-      where
-        (x, y) = S.breakByte 10 bs
+    close rest
+        | S.null rest = Done Nothing ()
+        | otherwise   = HaveOutput (Done Nothing ()) (return ()) rest
 
-    close front
-        | S.null bs = return []
-        | otherwise = return [bs]
-      where
-        bs = front S.empty
diff --git a/Data/Conduit/List.hs b/Data/Conduit/List.hs
--- a/Data/Conduit/List.hs
+++ b/Data/Conduit/List.hs
@@ -12,6 +12,8 @@
     ( -- * Sources
       sourceList
     , sourceNull
+    , unfold
+    , enumFromTo
       -- * Sinks
       -- ** Pure
     , fold
@@ -49,6 +51,7 @@
     , flip
     , seq
     , otherwise
+    , Enum (succ), Eq
     )
 import Data.Conduit
 import Data.Conduit.Internal (pipeClose, runFinalize)
@@ -56,6 +59,39 @@
 import Data.Void (absurd)
 import Control.Monad (liftM, liftM2)
 
+-- | Generate a source from a seed value.
+--
+-- Since 0.4.2
+unfold :: Monad m
+       => (b -> Maybe (a, b))
+       -> b
+       -> Source m a
+unfold f =
+    go
+  where
+    go seed =
+        case f seed of
+            Just (a, seed') -> HaveOutput (go seed') (return ()) a
+            Nothing -> Done Nothing ()
+
+-- | Enumerate from a value to a final value, inclusive, via 'succ'.
+--
+-- This is generally more efficient than using @Prelude@\'s @enumFromTo@ and
+-- combining with @sourceList@ since this avoids any intermediate data
+-- structures.
+--
+-- Since 0.4.2
+enumFromTo :: (Enum a, Eq a, Monad m)
+           => a
+           -> a
+           -> Source m a
+enumFromTo start stop =
+    go start
+  where
+    go i
+        | i == stop = HaveOutput (Done Nothing ()) (return ()) i
+        | otherwise = HaveOutput (go (succ i)) (return ()) i
+
 -- | A strict left fold.
 --
 -- Since 0.3.0
@@ -370,4 +406,3 @@
 
     HaveOutput _ _ o >< _                = absurd o
     _                >< HaveOutput _ _ o = absurd o
-
diff --git a/Data/Conduit/Text.hs b/Data/Conduit/Text.hs
--- a/Data/Conduit/Text.hs
+++ b/Data/Conduit/Text.hs
@@ -70,25 +70,20 @@
 -- Since 0.4.1
 lines :: Monad m => C.Conduit T.Text m T.Text
 lines =
-    C.conduitState id push close
+    C.NeedInput (push id) (close T.empty)
   where
-    push front bs' = return $ C.StateProducing leftover ls
-      where
-        bs = front bs'
-        (leftover, ls) = getLines id bs
-
-    getLines front bs
-        | T.null bs = (id, front [])
-        | T.null y = (T.append x, front [])
-        | otherwise = getLines (front . (x:)) (T.drop 1 y)
+    push sofar more =
+        case T.uncons second of
+            Just (_, second') -> C.HaveOutput (push id second') (return ()) (sofar first')
+            Nothing ->
+                let rest = sofar more
+                 in C.NeedInput (push $ T.append rest) (close rest)
       where
-        (x, y) = T.break (== '\n') bs
+        (first', second) = T.break (== '\n') more
 
-    close front
-        | T.null bs = return []
-        | otherwise = return [bs]
-      where
-        bs = front T.empty
+    close rest
+        | T.null rest = C.Done Nothing ()
+        | otherwise   = C.HaveOutput (C.Done Nothing ()) (return ()) rest
 
 -- | Convert text into bytes, using the provided codec. If the codec is
 -- not capable of representing an input character, an exception will be thrown.
diff --git a/conduit.cabal b/conduit.cabal
--- a/conduit.cabal
+++ b/conduit.cabal
@@ -1,8 +1,8 @@
 Name:                conduit
-Version:             0.4.1.1
+Version:             0.4.2
 Synopsis:            Streaming data processing library.
 Description:
-	Conduits are an approach to the streaming data problem. It is meant as an alternative to enumerators\/iterators, hoping to address the same issues with different trade-offs based on real-world experience with enumerators. For more information, see <http://www.yesodweb.com/book/conduit>.
+	Conduits are an approach to the streaming data problem. It is meant as an alternative to enumerators\/iterators, hoping to address the same issues with different trade-offs based on real-world experience with enumerators. For more information, see <http://www.yesodweb.com/book/conduits>.
 	.
 	Release history:
     .
diff --git a/test/main.hs b/test/main.hs
--- a/test/main.hs
+++ b/test/main.hs
@@ -83,6 +83,15 @@
             (runST $ CL.sourceList list C.$$ CL.fold (+) (0 :: Int))
             == sum list
 
+    describe "unfold" $ do
+        it "works" $ do
+            let f 0 = Nothing
+                f i = Just (show i, i - 1)
+                seed = 10 :: Int
+            x <- CL.unfold f seed C.$$ CL.consume
+            let y = DL.unfoldr f seed
+            x @?= y
+
     describe "Monoid instance for Source" $ do
         it "mappend" $ do
             x <- runResourceT $ (CL.sourceList [1..5 :: Int] `mappend` CL.sourceList [6..10]) C.$$ CL.fold (+) 0
