diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for data-elevator-forced
+
+## 0.1.0.0 -- YYYY-mm-dd
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2023 Ruben Astudillo
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/data-forced.cabal b/data-forced.cabal
new file mode 100644
--- /dev/null
+++ b/data-forced.cabal
@@ -0,0 +1,58 @@
+cabal-version:      3.0
+name:               data-forced
+version:            0.1.0.0
+synopsis:           Specify that lifted values were forced to WHNF or NF.
+license:            MIT
+license-file:       LICENSE
+author:             Ruben Astudillo
+maintainer:         ruben.astud@gmail.com
+copyright:          2023
+category:           Data
+build-type:         Simple
+extra-doc-files:    CHANGELOG.md
+description:
+  Alternative to bang patterns using CBV functions and unlifted data types.
+  Tag your values to maintain the invariant that they were forced. Avoid
+  liveness leaks on long running data structures.
+
+  > import Data.Map.Lazy as ML -- Spine strict
+  > 
+  > -- No references on added leafs even though it is a lazy map.
+  > basicEvent :: ML.Map Char (ForcedWHNF Int) -> IO (ML.Map Char (ForcedWHNF Int))
+  > basicEvent map0 = do
+  >   let val0 :: Strict (ForcedWHNF Int)
+  >       -- val0 = strictlyWHNF (error "argument evaluated") -- would fail
+  >       val0 = strictlyWHNF (2 + 2)
+  >       -- CBV function, 2 + 2 reduced before val0 is bound.
+  >       Strict val1 = val0  -- De-structure
+  >       map1 = ML.insert 'a' val1 map0
+  >   pure map1
+-- extra-source-files:
+
+common warnings
+    ghc-options: -Wall
+
+library
+    import:           warnings
+    exposed-modules:  Data.Forced
+    -- other-modules:
+    -- other-extensions:
+    build-depends:    base ^>=4.16.4.0,
+                      data-elevator >=0.1.0.0,
+                      deepseq >= 1.4.6.0
+    hs-source-dirs:   src
+    default-language: GHC2021
+
+test-suite data-forced-test
+    import:           warnings
+    default-language: GHC2021
+    -- other-modules:
+    -- other-extensions:
+    type:             exitcode-stdio-1.0
+    hs-source-dirs:   test
+    main-is:          Main.hs
+    build-depends:
+        base ^>=4.16.4.0,
+        containers,
+        HUnit,
+        data-forced
diff --git a/src/Data/Forced.hs b/src/Data/Forced.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Forced.hs
@@ -0,0 +1,43 @@
+{-# Language ExplicitForAll, UnliftedDatatypes, PatternSynonyms, GADTSyntax #-}
+
+module Data.Forced
+  ( Strict(..)
+  , ForcedWHNF
+  , pattern ForcedWHNF
+  , ForcedNF
+  , pattern ForcedNF
+  , strictlyWHNF
+  , strictlyNF
+  ) where
+
+import Data.Elevator ( UnliftedType, LiftedType )
+import Control.DeepSeq
+
+type Strict :: LiftedType -> UnliftedType
+data Strict a where
+  Strict :: !a -> Strict a
+
+{- The invariants of @ForcedWHNF@ and @ForcedNF@ depends on the constructors
+not being exported. The only way to construct these value is through the CBV
+functions. Pattern matching is done via a unidirectional pattern.
+-}
+newtype ForcedWHNF a = ForcedOuter a
+
+pattern ForcedWHNF :: forall a. a -> ForcedWHNF a
+pattern ForcedWHNF a <- ForcedOuter a
+
+newtype ForcedNF a = ForcedFull a
+
+pattern ForcedNF :: forall a. a -> ForcedNF a
+pattern ForcedNF a <- ForcedFull a
+
+{- | This is a CBV function. Evaluates the argument to WHNF before
+returning.
+-}
+strictlyWHNF :: forall a. a -> Strict (ForcedWHNF a)
+strictlyWHNF a = Strict (ForcedOuter a)
+
+{- | This is a CBV function. Evaluates the argument to NF before returning.
+-}
+strictlyNF :: forall a. NFData a => a -> Strict (ForcedNF a)
+strictlyNF a = Strict (ForcedFull (rnf a `seq` a))
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,51 @@
+{-# OPTIONS_GHC -Wno-incomplete-patterns -Wno-unused-binds #-}
+module Main (main) where
+
+import qualified Data.Map.Lazy as ML
+import Data.Forced
+import Test.HUnit ( (~:), runTestTT, Counts, Test(TestList) )
+import Control.Exception (catch, ErrorCall (..))
+
+main :: IO Counts
+main = runTestTT tests
+
+tests :: Test
+tests = TestList
+  [ "test1" ~: "noThunksForWHNF" ~: errorCalledWithMsg "argument evaluated" noThunksForWHNF
+  , "test2" ~: "thunksForWHNFMaybe" ~: thunksForWHNFMaybe
+  , "test3" ~: "noThunksForNFMaybe" ~: errorCalledWithMsg "argument evaluated" noThunksForNFMaybe
+  ]
+
+errorCalledWithMsg :: String -> IO a -> IO Bool
+errorCalledWithMsg msg io =
+  catch (False <$ io) (\(ErrorCall msg1) -> pure (msg == msg1))
+
+noThunksForWHNF :: IO ()
+noThunksForWHNF = do
+  let map0 :: ML.Map Char (ForcedWHNF Int)
+      map0 = ML.empty
+      val0 :: Strict (ForcedWHNF Int)
+      val0 = strictlyWHNF (error "argument evaluated")
+      Strict val1 = val0
+      map1 = ML.insert 'a' val1 map0
+  pure ()
+
+thunksForWHNFMaybe :: IO ()
+thunksForWHNFMaybe = do
+  let map0 :: ML.Map Char (ForcedWHNF (Maybe Int))
+      map0 = ML.empty
+      val0 :: Strict (ForcedWHNF (Maybe Int))
+      val0 = strictlyWHNF (const (Just (error "argument evaluated")) 'a')
+      Strict val1 = val0
+      map1 = ML.insert 'a' val1 map0
+  pure ()
+
+noThunksForNFMaybe :: IO ()
+noThunksForNFMaybe = do
+  let map0 :: ML.Map Char (ForcedNF (Maybe Int))
+      map0 = ML.empty
+      val0 :: Strict (ForcedNF (Maybe Int))
+      val0 = strictlyNF ((+1) <$> Just (error "argument evaluated" :: Int))
+      Strict val = val0
+      map1 = ML.insert 'a' val map0
+  pure ()
