diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Alexey Kotlyarov (c) 2017
+
+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 Alexey Kotlyarov 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,7 @@
+# simpleconfig
+
+### Releasing
+
+* Install [bumpversion](https://github.com/peritus/bumpversion): `pip install bumpversion`.
+* Run `bumpversion major|minor|patch`.
+* Run `git push --tags`.
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/simpleconfig.cabal b/simpleconfig.cabal
new file mode 100644
--- /dev/null
+++ b/simpleconfig.cabal
@@ -0,0 +1,57 @@
+-- This file has been generated from package.yaml by hpack version 0.20.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: d5a310c8f0c4aa16e1b8046c54841213c3110fb60919b3737484fbeb7c5cac12
+
+name:                simpleconfig
+version:             0.0.1
+synopsis:            Short description of your package
+homepage:            https://github.com/koterpillar/simpleconfig#readme
+bug-reports:         https://github.com/koterpillar/simpleconfig/issues
+license:             BSD3
+license-file:        LICENSE
+author:              Alexey Kotlyarov
+maintainer:          a@koterpillar.com
+copyright:           2017 Alexey Kotlyarov
+category:            Web
+build-type:          Simple
+cabal-version:       >= 1.10
+description:         Please see the README on Github at <https://github.com/koterpillar/simpleconfig#readme>
+
+extra-source-files:
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/koterpillar/simpleconfig
+
+library
+  hs-source-dirs:
+      src
+  exposed-modules:
+      Config.Simple
+  other-modules:
+      Paths_simpleconfig
+  build-depends:
+      base >=4.7 && <5
+    , containers
+    , lens
+  default-language: Haskell2010
+
+test-suite simpleconfig-test
+  type: exitcode-stdio-1.0
+  hs-source-dirs:
+      test
+  main-is: Spec.hs
+  build-depends:
+      base
+    , containers
+    , generic-deriving
+    , lens
+    , simpleconfig
+    , text
+  other-modules:
+      Paths_simpleconfig
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  default-language: Haskell2010
diff --git a/src/Config/Simple.hs b/src/Config/Simple.hs
new file mode 100644
--- /dev/null
+++ b/src/Config/Simple.hs
@@ -0,0 +1,171 @@
+{-|
+Module: Config.Simple
+Description: Simple configuration data types
+
+Functions for declaring a configuration data type.
+-}
+
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+module Config.Simple
+  ( ConfigBool
+  , ConfigLast
+  , ConfigSet
+  , Partial
+  , Complete
+  , LensFor(..)
+  , configLens
+  , configLensPartial
+  , fromPartialConfig
+  ) where
+
+import Control.Lens
+
+import Data.Monoid (Any(..), Last(..))
+import Data.Set (Set)
+
+import GHC.Generics
+import qualified GHC.Generics as G
+import qualified GHC.Generics.Lens as G
+
+import Control.Applicative
+
+data CPartial
+
+data CComplete
+
+data CLensFor k c
+
+newtype LensFor s a =
+  LensFor (Lens' s a)
+
+type family ConfigLast a k where
+  ConfigLast a CPartial = Last a
+  ConfigLast a CComplete = a
+  ConfigLast a (CLensFor CPartial root) = LensFor root (Maybe a)
+  ConfigLast a (CLensFor CComplete root) = LensFor root a
+
+type family ConfigBool k where
+  ConfigBool CPartial = Any
+  ConfigBool CComplete = Bool
+  ConfigBool (CLensFor CPartial root) = LensFor root Bool
+  ConfigBool (CLensFor CComplete root) = LensFor root Bool
+
+type family ConfigSet a k where
+  ConfigSet a CPartial = Set a
+  ConfigSet a CComplete = Set a
+  ConfigSet a (CLensFor CPartial root) = LensFor root (Set a)
+  ConfigSet a (CLensFor CComplete root) = LensFor root (Set a)
+
+type Partial config = config CPartial
+
+type Complete config = config CComplete
+
+type LensConfig k config = config (CLensFor k (config k))
+
+fromPartialConfig ::
+     ( Generic (Partial config)
+     , Generic (Complete config)
+     , GFromPartialConfig (Rep (Partial config)) (Rep (Complete config))
+     )
+  => Partial config
+  -> Maybe (Complete config)
+fromPartialConfig = fmap G.to . gFromPartialConfig . G.from
+
+class GFromPartialConfig (repPartial :: * -> *) (repComplete :: * -> *) where
+  gFromPartialConfig :: repPartial x -> Maybe (repComplete x)
+
+instance GFromPartialConfig fp fc =>
+         GFromPartialConfig (M1 i m fp) (M1 i m fc) where
+  gFromPartialConfig (M1 x) = M1 <$> gFromPartialConfig x
+
+instance (GFromPartialConfig ap ac, GFromPartialConfig bp bc) =>
+         GFromPartialConfig (ap :*: bp) (ac :*: bc) where
+  gFromPartialConfig (a :*: b) =
+    liftA2 (:*:) (gFromPartialConfig a) (gFromPartialConfig b)
+
+class GFromPartialConfigMember partial complete where
+  gFromPartialConfigMember :: partial -> Maybe complete
+
+instance GFromPartialConfigMember partial complete =>
+         GFromPartialConfig (Rec0 partial) (Rec0 complete) where
+  gFromPartialConfig (K1 a) = K1 <$> gFromPartialConfigMember a
+
+instance GFromPartialConfigMember Any Bool where
+  gFromPartialConfigMember = Just . getAny
+
+instance GFromPartialConfigMember (Set a) (Set a) where
+  gFromPartialConfigMember = Just
+
+instance GFromPartialConfigMember (Last a) a where
+  gFromPartialConfigMember = getLast
+
+configLens' ::
+     forall config k proxy.
+     ( Generic (config k)
+     , Generic (LensConfig k config)
+     , GLensFor k (config k) (Rep (config k)) (Rep (LensConfig k config))
+     )
+  => proxy k
+  -> LensConfig k config
+configLens' pk = G.to $ gToLensFor pk rootLens
+  where
+    rootLens ::
+         forall x. Generic (config k)
+      => Lens' (config k) (Rep (config k) x)
+    rootLens = G.generic
+
+configLensPartial ::
+     forall config proxy.
+     ( Generic (config CPartial)
+     , Generic (LensConfig CPartial config)
+     , GLensFor CPartial (config CPartial) (Rep (config CPartial)) (Rep (LensConfig CPartial config))
+     )
+  => LensConfig CPartial config
+configLensPartial = configLens' (Nothing :: Maybe CPartial)
+
+configLens ::
+     forall config proxy.
+     ( Generic (config CComplete)
+     , Generic (LensConfig CComplete config)
+     , GLensFor CComplete (config CComplete) (Rep (config CComplete)) (Rep (LensConfig CComplete config))
+     )
+  => LensConfig CComplete config
+configLens = configLens' (Nothing :: Maybe CComplete)
+
+class GLensFor k root rep repLens where
+  gToLensFor :: proxy k -> Lens' root (rep x) -> repLens x
+
+instance GLensFor k root r rl => GLensFor k root (M1 i m r) (M1 i m rl) where
+  gToLensFor pk rootLens = M1 $ gToLensFor pk (rootLens . G._M1)
+
+instance (GLensFor k root ra ral, GLensFor k root rb rbl) =>
+         GLensFor k root (ra :*: rb) (ral :*: rbl) where
+  gToLensFor pk rootLens =
+    gToLensFor pk (rootLens . _1) :*: gToLensFor pk (rootLens . _2)
+
+instance GLensForMember k x y =>
+         GLensFor k root (Rec0 x) (Rec0 (LensFor root y)) where
+  gToLensFor pk rootLens = K1 $ LensFor $ rootLens . G._K1 . gLensLeaf pk
+
+class GLensForMember k x y where
+  gLensLeaf :: proxy k -> Lens' x y
+
+instance GLensForMember CPartial (Last a) (Maybe a) where
+  gLensLeaf _ = iso getLast Last
+
+instance GLensForMember CPartial Any Bool where
+  gLensLeaf _ = iso getAny Any
+
+instance GLensForMember CPartial (Set a) (Set a) where
+  gLensLeaf _ = id
+
+instance GLensForMember CComplete a a where
+  gLensLeaf _ = id
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,64 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE TypeOperators #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+import Control.Lens
+import Control.Monad
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.IO as Text
+import Data.Foldable
+
+import Generics.Deriving.Monoid
+
+import GHC.Generics
+
+import Config.Simple
+
+data ConfigF k = Config
+  { _address :: ConfigLast Text k
+  , _dryRun :: ConfigBool k
+  , _widgets :: ConfigSet Text k
+  } deriving (Generic)
+
+type PartialConfig = Partial ConfigF
+
+deriving instance Eq PartialConfig
+
+deriving instance Show PartialConfig
+
+instance Monoid PartialConfig where
+  mempty = memptydefault
+  mappend = mappenddefault
+
+Config (LensFor address') (LensFor dryRun') (LensFor widgets') =
+  configLensPartial
+
+type Config = Complete ConfigF
+
+deriving instance Eq Config
+
+deriving instance Show Config
+
+Config (LensFor address) (LensFor dryRun) (LensFor widgets) = configLens
+
+main :: IO ()
+main = do
+  let config' =
+        mempty & address' <>~ pure "Silverpond" & dryRun' .~ True & widgets' <>~
+        Set.singleton "blah"
+  print config'
+  let (Just config) = fromPartialConfig config'
+  Text.putStrLn $ "Address = " <> config ^. address
+  when (config ^. dryRun) $ Text.putStrLn "Dry run"
+  for_ (config ^. widgets) $ \widget ->
+    Text.putStrLn $ "Widget = " <> widget
