diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for bidirectional
+
+## 0.1.0.0 -- 2020-10-07
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2020, Mats Rauhala
+
+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 Mats Rauhala 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.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# Bidirectional - bidirectional parsing
+
+This library is based on the [blog](https://blog.poisson.chat/posts/2017-01-01-monadic-profunctors.html) by Lysxia.
+
+In it they define a bidirectional parser that can both generate and consume
+values.
+
+Imagine that you have a record `Person` and you want to serialize it into a
+list and deserialize it back.
+
+``` haskell
+data Person = Person { name :: String, age :: Int }
+```
+
+
+The parser is parameterized over the parsing and generation contexts, these
+needs to be selected carefully when implementing the parsers. It could be for
+example a `RowParser` `Writer [SQLData]` combination for sqlite-simple, or a
+simple state and writer for our running example.
+
+So, let's figure out the context for our example. We want to encode into a list
+and then decode the list back into a value.
+
+``` haskell
+type SimpleParser a = IParser (StateT [String] Maybe) (Writer [String]) a a
+```
+
+Then we create the parsers using the `parser` function.
+
+``` haskell
+int :: SimpleParser Int
+int =
+  parser
+    (StateT $ \(x:xs) -> (,xs) <$> readMaybe x)
+    (\x -> x <$ tell [show x])
+
+string :: SimpleParser String
+string =
+  parser
+    (StateT $ \(x:xs) -> Just (x,xs))
+    (\x -> x <$ tell [x])
+```
+
+One small detail for creating records, is that the encoder gets the full record
+structure by default, so you need to focus in on a specific part of the record
+using the `(.=)` function.
+
+``` haskell
+person :: SimpleParser Person
+person =
+  Person <$> name .= string
+         <*> age .= int
+```
+
+And now you can use your encoders and decoders
+
+```
+> execWriter (encode person (Person "foo" 30))
+["foo", "30"]
+
+> evalStateT (decode person ["foo", "30"])
+Just (Person { name = "foo", age = 30 })
+```
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/bidirectional.cabal b/bidirectional.cabal
new file mode 100644
--- /dev/null
+++ b/bidirectional.cabal
@@ -0,0 +1,42 @@
+cabal-version:       2.0
+-- Initial package description 'bidirectional.cabal' generated by 'cabal
+-- init'.  For further documentation, see
+-- http://haskell.org/cabal/users-guide/
+
+name:                bidirectional
+version:             0.1.0.0
+synopsis:            Simple bidirectional serialization and deserialization
+description:         Bidirectional serialization based on https://blog.poisson.chat/posts/2017-01-01-monadic-profunctors.html
+bug-reports:         https://github.com/MasseR/bidirectional/issues
+license:             BSD3
+license-file:        LICENSE
+author:              Mats Rauhala
+maintainer:          mats.rauhala@iki.fi
+-- copyright:
+category:            Codec
+build-type:          Simple
+extra-source-files:  CHANGELOG.md
+                     README.md
+
+source-repository head
+  type: git
+  location: https://github.com/MasseR/bidirectional
+
+library
+  exposed-modules:     Data.IParser
+  -- other-modules:
+  -- other-extensions:
+  build-depends:       base >=4.10.0.0 && < 4.14
+                     , profunctors >= 5 && < 5.6
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite bidirectional-test
+  default-language:    Haskell2010
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Main.hs
+  build-depends:       base >=4.10.0.0 && < 4.14
+                     , hedgehog
+                     , bidirectional
+                     , mtl
diff --git a/src/Data/IParser.hs b/src/Data/IParser.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/IParser.hs
@@ -0,0 +1,94 @@
+{-# LANGUAGE Safe #-}
+{-|
+Module      : Data.IParser
+Description : Bidirectional parsing
+Copyright   : (c) Mats Rauhala, 2020
+License     : BSD-3
+Maintainer  : mats.rauhala@iki.fi
+Stability   : experimental
+Portability : POSIX
+
+Let's assume we have a parser like the following
+
+@
+int :: Parser (ReaderT String Maybe) (Writer [Int]) Int Int
+int = parser (ReaderT readMaybe) (\x -> x <$ tell [show x])
+@
+
+Then you can use the parser for parsing:
+
+@
+> runReaderT (decode int) "3"
+Just 3
+@
+
+Or for encoding:
+
+@
+> execWriter (encode int 3)
+["3"]
+@
+
+Or combine both of them
+
+@
+> runReaderT (decode int) $ head $ execWriter $ encode int 3
+Just 3
+@
+-}
+module Data.IParser where
+
+import Data.Profunctor
+
+-- | The core bidirectional parser type
+--
+-- See the module for usage example
+--
+data IParser r w c a =
+  IParser { decoder :: r a -- ^ Return a value "a" in context "r".
+          , encoder :: Star w c a -- ^ Generate a value "a" from the initial object "c" in the context "w"
+          }
+
+-- | Smart constructor for the parser
+parser
+  :: r a -- ^ The parser
+  -> (c -> w a) -- ^ The encoder
+  -> IParser r w c a
+parser dec enc = IParser { decoder = dec, encoder = Star enc }
+
+-- | Extract the decoder from the parser
+decode :: IParser r w c a -> r a
+decode = decoder
+
+-- | Extract the encoder from the parser
+encode :: IParser r w c a -> c -> w a
+encode = runStar . encoder
+
+-- | Record accessor helper
+--
+-- Due to the nature of the parser, the encoder gets the full record type, when
+-- it should only focus on a specific part.
+--
+-- @
+-- data Person = Person { name :: String, age :: Int }
+--
+-- person :: IParser r w Person Person
+-- person =
+--  Person <$> name .= string
+--         <*> age .= int
+-- @
+(.=) :: (Functor r, Functor w) => (c -> c') -> IParser r w c' a -> IParser r w c a
+(.=) = lmap
+
+instance (Functor w, Functor r) => Functor (IParser r w c) where
+  fmap f i = IParser { decoder = f <$> decoder i
+                     , encoder = f <$> encoder i
+                     }
+
+instance (Applicative w, Applicative r) => Applicative (IParser r w c) where
+  pure x = IParser { decoder = pure x, encoder = pure x }
+  fx <*> x = IParser { decoder = decoder fx <*> decoder x, encoder = encoder fx <*> encoder x }
+
+instance (Functor r, Functor w) => Profunctor (IParser r w) where
+  dimap f g i = IParser { decoder = g <$> decoder i, encoder = dimap f g (encoder i) }
+
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TupleSections #-}
+module Main (main) where
+
+import System.Exit
+       (exitFailure, exitSuccess)
+
+import Data.Functor.Identity
+
+import Hedgehog
+
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+
+import Control.Monad.Reader
+       (ReaderT(..), runReaderT)
+import Control.Monad.State
+       (StateT(..), evalStateT)
+import Control.Monad.Writer
+       (Writer, execWriter, tell)
+import Text.Read
+       (readMaybe)
+
+import Data.IParser
+
+-- fmap id == id
+prop_functor_identity :: Property
+prop_functor_identity = property $ do
+  x <- forAll $ Gen.integral (Range.linear 0 100)
+  let p = parser (pure x) pure :: IParser Identity Identity Int Int
+  decode (fmap id p) === id (decode p)
+  encode (fmap id p) x === id (encode p x)
+
+-- fmap (f . g) == fmap f . fmap g
+prop_functor_composition :: Property
+prop_functor_composition = property $ do
+  x <- forAll $ Gen.integral (Range.linear 0 100)
+  y <- forAll $ Gen.integral (Range.linear 0 100)
+  z <- forAll $ Gen.integral (Range.linear 0 100)
+  let p = parser (pure val) pure :: IParser Identity Identity (Int, (Int, Int)) (Int, (Int, Int))
+      f = fst
+      g = snd
+      val = (x,(y,z))
+  decode (fmap (f . g) p) === decode (fmap f . fmap g $ p)
+  encode (fmap (f . g) p) val === encode (fmap f . fmap g $ p) val
+
+-- pure id <*> v
+prop_applicative_identity :: Property
+prop_applicative_identity = property $ do
+  x <- forAll $ Gen.integral (Range.linear 0 100)
+  let p = pure x :: IParser Identity Identity Int Int
+  decode (pure id <*> p) === pure x
+
+prop_bidirectional :: Property
+prop_bidirectional = property $ do
+  x <- forAll $ Gen.integral (Range.linear 0 100)
+  let int = parser (ReaderT readMaybe) (\x -> x <$ tell [show x])
+  runReaderT (decode int) (head (execWriter (encode int x))) === Just x
+
+data Person
+  = Person { name :: String, age :: Int }
+  deriving (Show, Eq)
+
+prop_compose :: Property
+prop_compose = property $ do
+  person <- forAll (Person <$> Gen.string (Range.linear 0 10) Gen.unicode <*> Gen.integral (Range.linear 10 30))
+  let int = parser (StateT $ \(x:xs) -> (,xs) <$> readMaybe x) (\x -> x <$ tell [show x])
+      string = parser (StateT $ \(x:xs) -> Just (x,xs)) (\x -> x <$ tell [x])
+      p = Person <$> name .= string <*> age .= int
+      encoded = execWriter (encode p person)
+  evalStateT (decode p) encoded === Just person
+
+main :: IO ()
+main = do
+  result <- checkParallel $$(discover)
+  if result then exitSuccess else exitFailure
