diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2014 Oleg Grenrus
+
+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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,15 @@
+# Boolean normal forms
+
+[![Build Status](https://travis-ci.org/phadej/boolean-normal-forms.svg?branch=master)](https://travis-ci.org/phadej/boolean-normal-forms)
+
+This library provides types that might be useful in symbolic manipulation of propositional logic expressions.
+
+## Features
+
+- **CNF** (list and set)
+- **DNF** (list and set)
+- **NNF** (tree and set)
+- FreeBoolean type useful in testing
+
+Distributed under MIT license
+© Oleg Grenrus 2014
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/boolean-normal-forms.cabal b/boolean-normal-forms.cabal
new file mode 100644
--- /dev/null
+++ b/boolean-normal-forms.cabal
@@ -0,0 +1,70 @@
+name:                boolean-normal-forms
+version:             0.0.0.1
+synopsis:            Boolean normal form: NNF, DNF & CNF
+description:         The provided types that might be useful in symbolic manipulation of propositional logic expressions.
+license:             MIT
+license-file:        LICENSE
+author:              Oleg Grenrus
+maintainer:          oleg.grenrus@iki.fi
+copyright:           © Oleg Grenrus 2014
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  README.md
+extra-doc-files:      doc-formulae/*.svg
+
+library
+  hs-source-dirs:      src
+  exposed-modules:     Data.Algebra.Boolean.NormalForm,
+                       Data.Algebra.Boolean.FreeBoolean,
+                       Data.Algebra.Boolean.NNF,
+                       Data.Algebra.Boolean.DNF,
+                       Data.Algebra.Boolean.CNF,
+                       Data.Algebra.Boolean.Negable,
+                       Data.Algebra.Boolean.CoBoolean,
+                       Data.Algebra.Boolean.NNF.Set,
+                       Data.Algebra.Boolean.NNF.Tree,
+                       Data.Algebra.Boolean.DNF.List,
+                       Data.Algebra.Boolean.DNF.Set,
+                       Data.Algebra.Boolean.CNF.List,
+                       Data.Algebra.Boolean.CNF.Set
+  other-extensions:    DeriveFunctor,
+                       DeriveDataTypeable,
+                       ConstraintKinds,
+                       TypeFamilies,
+                       ScopedTypeVariables
+  build-depends:       base >=4.6 && <4.8,
+                       containers >=0.5 && <0.6,
+                       cond >=0.4.1 && <0.5
+  default-language:    Haskell2010
+  ghc-options:         -Wall
+                       -O2
+                       -fwarn-tabs
+                       -fspec-constr-count=6
+                       -fmax-simplifier-iterations=10
+                       -fdicts-cheap
+
+test-suite test
+  default-language:    Haskell2010
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      tests
+  main-is:             Main.hs
+  other-modules:       BooleanLaws,
+                       CNFList,
+                       CNFSet,
+                       DNFList,
+                       DNFSet,
+                       FreeBoolean,
+                       FreeBooleanTests,
+                       NegableLaws,
+                       NNFSet,
+                       NNFTree,
+                       SimplifyLaws
+  ghc-options:         -Wall -O2 -fno-warn-orphans
+  build-depends:       base >=4.6 && <4.8,
+                       containers >= 0.5 && <0.6,
+                       cond >=0.4.1 && <0.5,
+                       tasty >=0.8,
+                       tasty-quickcheck >=0.8.0.3,
+                       QuickCheck >=2.7 && <=2.8,
+                       boolean-normal-forms
diff --git a/doc-formulae/cnf.svg b/doc-formulae/cnf.svg
new file mode 100644
--- /dev/null
+++ b/doc-formulae/cnf.svg
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='ISO-8859-1'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- This file was generated by dvisvgm 1.0.11 () -->
+<!-- Sat May 10 16:17:17 2014 -->
+<svg height='23.6598pt' version='1.1' viewBox='215.558 74.4999 37.3843 23.6598' width='37.3843pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
+<defs>
+<path d='M2.268 -4.375C2.268 -4.487 2.184 -4.641 1.988 -4.641C1.799 -4.641 1.596 -4.459 1.596 -4.256C1.596 -4.137 1.687 -3.99 1.876 -3.99C2.079 -3.99 2.268 -4.186 2.268 -4.375ZM0.84 -0.819C0.812 -0.728 0.777 -0.644 0.777 -0.525C0.777 -0.196 1.057 0.07 1.442 0.07C2.142 0.07 2.45 -0.896 2.45 -1.001C2.45 -1.092 2.359 -1.092 2.338 -1.092C2.24 -1.092 2.233 -1.05 2.205 -0.973C2.044 -0.413 1.736 -0.126 1.463 -0.126C1.323 -0.126 1.288 -0.217 1.288 -0.371C1.288 -0.532 1.337 -0.665 1.4 -0.819C1.47 -1.008 1.547 -1.197 1.624 -1.379C1.687 -1.547 1.939 -2.184 1.967 -2.268C1.988 -2.338 2.009 -2.422 2.009 -2.492C2.009 -2.821 1.729 -3.087 1.344 -3.087C0.651 -3.087 0.329 -2.135 0.329 -2.016C0.329 -1.925 0.427 -1.925 0.448 -1.925C0.546 -1.925 0.553 -1.96 0.574 -2.037C0.756 -2.639 1.064 -2.891 1.323 -2.891C1.435 -2.891 1.498 -2.835 1.498 -2.646C1.498 -2.485 1.456 -2.38 1.281 -1.946L0.84 -0.819Z' id='g1-105'/>
+<path d='M3.073 -4.375C3.073 -4.494 2.982 -4.641 2.793 -4.641C2.59 -4.641 2.401 -4.445 2.401 -4.256C2.401 -4.144 2.485 -3.99 2.681 -3.99C2.87 -3.99 3.073 -4.172 3.073 -4.375ZM1.589 0.343C1.47 0.826 1.099 1.232 0.686 1.232C0.595 1.232 0.511 1.218 0.434 1.19C0.616 1.106 0.672 0.938 0.672 0.833C0.672 0.665 0.539 0.574 0.399 0.574C0.182 0.574 0 0.763 0 0.987C0 1.253 0.273 1.428 0.693 1.428S1.932 1.176 2.149 0.322L2.779 -2.184C2.8 -2.261 2.814 -2.324 2.814 -2.429C2.814 -2.814 2.485 -3.087 2.065 -3.087C1.288 -3.087 0.84 -2.114 0.84 -2.016C0.84 -1.925 0.938 -1.925 0.959 -1.925C1.043 -1.925 1.05 -1.946 1.099 -2.051C1.274 -2.457 1.638 -2.891 2.044 -2.891C2.219 -2.891 2.282 -2.772 2.282 -2.548C2.282 -2.471 2.268 -2.373 2.261 -2.338L1.589 0.343Z' id='g1-106'/>
+<path d='M3.73 -3.78C3.55 -4.15 3.26 -4.42 2.81 -4.42C1.64 -4.42 0.4 -2.95 0.4 -1.49C0.4 -0.55 0.95 0.11 1.73 0.11C1.93 0.11 2.43 0.07 3.03 -0.64C3.11 -0.22 3.46 0.11 3.94 0.11C4.29 0.11 4.52 -0.12 4.68 -0.44C4.85 -0.8 4.98 -1.41 4.98 -1.43C4.98 -1.53 4.89 -1.53 4.86 -1.53C4.76 -1.53 4.75 -1.49 4.72 -1.35C4.55 -0.7 4.37 -0.11 3.96 -0.11C3.69 -0.11 3.66 -0.37 3.66 -0.57C3.66 -0.79 3.68 -0.87 3.79 -1.31C3.9 -1.73 3.92 -1.83 4.01 -2.21L4.37 -3.61C4.44 -3.89 4.44 -3.91 4.44 -3.95C4.44 -4.12 4.32 -4.22 4.15 -4.22C3.91 -4.22 3.76 -4 3.73 -3.78ZM3.08 -1.19C3.03 -1.01 3.03 -0.99 2.88 -0.82C2.44 -0.27 2.03 -0.11 1.75 -0.11C1.25 -0.11 1.11 -0.66 1.11 -1.05C1.11 -1.55 1.43 -2.78 1.66 -3.24C1.97 -3.83 2.42 -4.2 2.82 -4.2C3.47 -4.2 3.61 -3.38 3.61 -3.32S3.59 -3.2 3.58 -3.15L3.08 -1.19Z' id='g0-97'/>
+<path d='M9.7 13.62C9.75 13.76 9.83 14 10.12 14C10.36 14 10.54 13.81 10.54 13.58C10.54 13.53 10.54 13.51 10.48 13.35L5.93 0.23C5.86 0.09 5.71 0 5.55 0C5.26 0 5.17 0.24 5.13 0.37L0.62 13.35C0.56 13.51 0.56 13.53 0.56 13.58C0.56 13.82 0.75 14 0.98 14C1.27 14 1.34 13.77 1.39 13.64L5.55 1.68L9.7 13.62Z' id='g2-94'/>
+<path d='M5.13 13.63C5.17 13.76 5.26 14 5.55 14S5.92 13.77 5.97 13.63L10.48 0.65C10.54 0.49 10.54 0.47 10.54 0.42C10.54 0.18 10.35 0 10.12 0C9.83 0 9.76 0.23 9.71 0.36L5.55 12.32L1.4 0.38C1.35 0.24 1.27 0 0.98 0C0.74 0 0.56 0.19 0.56 0.42C0.56 0.47 0.56 0.49 0.62 0.65L5.13 13.63Z' id='g2-95'/>
+</defs>
+<g id='page1'>
+<use x='215.558' xlink:href='#g2-94' y='75.4999'/>
+<use x='219.699' xlink:href='#g1-105' y='96.7986'/>
+<use x='228.336' xlink:href='#g2-95' y='75.4999'/>
+<use x='232.034' xlink:href='#g1-106' y='96.7986'/>
+<use x='241.113' xlink:href='#g0-97' y='85'/>
+<use x='246.399' xlink:href='#g1-105' y='86.5'/>
+<use x='249.229' xlink:href='#g1-106' y='86.5'/>
+</g>
+</svg>
diff --git a/doc-formulae/dnf.svg b/doc-formulae/dnf.svg
new file mode 100644
--- /dev/null
+++ b/doc-formulae/dnf.svg
@@ -0,0 +1,22 @@
+<?xml version='1.0' encoding='ISO-8859-1'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- This file was generated by dvisvgm 1.0.11 () -->
+<!-- Sat May 10 16:17:16 2014 -->
+<svg height='23.6598pt' version='1.1' viewBox='215.558 74.4999 37.3843 23.6598' width='37.3843pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
+<defs>
+<path d='M3.73 -3.78C3.55 -4.15 3.26 -4.42 2.81 -4.42C1.64 -4.42 0.4 -2.95 0.4 -1.49C0.4 -0.55 0.95 0.11 1.73 0.11C1.93 0.11 2.43 0.07 3.03 -0.64C3.11 -0.22 3.46 0.11 3.94 0.11C4.29 0.11 4.52 -0.12 4.68 -0.44C4.85 -0.8 4.98 -1.41 4.98 -1.43C4.98 -1.53 4.89 -1.53 4.86 -1.53C4.76 -1.53 4.75 -1.49 4.72 -1.35C4.55 -0.7 4.37 -0.11 3.96 -0.11C3.69 -0.11 3.66 -0.37 3.66 -0.57C3.66 -0.79 3.68 -0.87 3.79 -1.31C3.9 -1.73 3.92 -1.83 4.01 -2.21L4.37 -3.61C4.44 -3.89 4.44 -3.91 4.44 -3.95C4.44 -4.12 4.32 -4.22 4.15 -4.22C3.91 -4.22 3.76 -4 3.73 -3.78ZM3.08 -1.19C3.03 -1.01 3.03 -0.99 2.88 -0.82C2.44 -0.27 2.03 -0.11 1.75 -0.11C1.25 -0.11 1.11 -0.66 1.11 -1.05C1.11 -1.55 1.43 -2.78 1.66 -3.24C1.97 -3.83 2.42 -4.2 2.82 -4.2C3.47 -4.2 3.61 -3.38 3.61 -3.32S3.59 -3.2 3.58 -3.15L3.08 -1.19Z' id='g0-97'/>
+<path d='M2.268 -4.375C2.268 -4.487 2.184 -4.641 1.988 -4.641C1.799 -4.641 1.596 -4.459 1.596 -4.256C1.596 -4.137 1.687 -3.99 1.876 -3.99C2.079 -3.99 2.268 -4.186 2.268 -4.375ZM0.84 -0.819C0.812 -0.728 0.777 -0.644 0.777 -0.525C0.777 -0.196 1.057 0.07 1.442 0.07C2.142 0.07 2.45 -0.896 2.45 -1.001C2.45 -1.092 2.359 -1.092 2.338 -1.092C2.24 -1.092 2.233 -1.05 2.205 -0.973C2.044 -0.413 1.736 -0.126 1.463 -0.126C1.323 -0.126 1.288 -0.217 1.288 -0.371C1.288 -0.532 1.337 -0.665 1.4 -0.819C1.47 -1.008 1.547 -1.197 1.624 -1.379C1.687 -1.547 1.939 -2.184 1.967 -2.268C1.988 -2.338 2.009 -2.422 2.009 -2.492C2.009 -2.821 1.729 -3.087 1.344 -3.087C0.651 -3.087 0.329 -2.135 0.329 -2.016C0.329 -1.925 0.427 -1.925 0.448 -1.925C0.546 -1.925 0.553 -1.96 0.574 -2.037C0.756 -2.639 1.064 -2.891 1.323 -2.891C1.435 -2.891 1.498 -2.835 1.498 -2.646C1.498 -2.485 1.456 -2.38 1.281 -1.946L0.84 -0.819Z' id='g1-105'/>
+<path d='M3.073 -4.375C3.073 -4.494 2.982 -4.641 2.793 -4.641C2.59 -4.641 2.401 -4.445 2.401 -4.256C2.401 -4.144 2.485 -3.99 2.681 -3.99C2.87 -3.99 3.073 -4.172 3.073 -4.375ZM1.589 0.343C1.47 0.826 1.099 1.232 0.686 1.232C0.595 1.232 0.511 1.218 0.434 1.19C0.616 1.106 0.672 0.938 0.672 0.833C0.672 0.665 0.539 0.574 0.399 0.574C0.182 0.574 0 0.763 0 0.987C0 1.253 0.273 1.428 0.693 1.428S1.932 1.176 2.149 0.322L2.779 -2.184C2.8 -2.261 2.814 -2.324 2.814 -2.429C2.814 -2.814 2.485 -3.087 2.065 -3.087C1.288 -3.087 0.84 -2.114 0.84 -2.016C0.84 -1.925 0.938 -1.925 0.959 -1.925C1.043 -1.925 1.05 -1.946 1.099 -2.051C1.274 -2.457 1.638 -2.891 2.044 -2.891C2.219 -2.891 2.282 -2.772 2.282 -2.548C2.282 -2.471 2.268 -2.373 2.261 -2.338L1.589 0.343Z' id='g1-106'/>
+<path d='M9.7 13.62C9.75 13.76 9.83 14 10.12 14C10.36 14 10.54 13.81 10.54 13.58C10.54 13.53 10.54 13.51 10.48 13.35L5.93 0.23C5.86 0.09 5.71 0 5.55 0C5.26 0 5.17 0.24 5.13 0.37L0.62 13.35C0.56 13.51 0.56 13.53 0.56 13.58C0.56 13.82 0.75 14 0.98 14C1.27 14 1.34 13.77 1.39 13.64L5.55 1.68L9.7 13.62Z' id='g2-94'/>
+<path d='M5.13 13.63C5.17 13.76 5.26 14 5.55 14S5.92 13.77 5.97 13.63L10.48 0.65C10.54 0.49 10.54 0.47 10.54 0.42C10.54 0.18 10.35 0 10.12 0C9.83 0 9.76 0.23 9.71 0.36L5.55 12.32L1.4 0.38C1.35 0.24 1.27 0 0.98 0C0.74 0 0.56 0.19 0.56 0.42C0.56 0.47 0.56 0.49 0.62 0.65L5.13 13.63Z' id='g2-95'/>
+</defs>
+<g id='page1'>
+<use x='215.558' xlink:href='#g2-95' y='75.4999'/>
+<use x='219.699' xlink:href='#g1-105' y='96.7986'/>
+<use x='228.336' xlink:href='#g2-94' y='75.4999'/>
+<use x='232.034' xlink:href='#g1-106' y='96.7986'/>
+<use x='241.113' xlink:href='#g0-97' y='85'/>
+<use x='246.399' xlink:href='#g1-105' y='86.5'/>
+<use x='249.229' xlink:href='#g1-106' y='86.5'/>
+</g>
+</svg>
diff --git a/doc-formulae/nnf.svg b/doc-formulae/nnf.svg
new file mode 100644
--- /dev/null
+++ b/doc-formulae/nnf.svg
@@ -0,0 +1,59 @@
+<?xml version='1.0' encoding='ISO-8859-1'?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- This file was generated by dvisvgm 1.0.11 () -->
+<!-- Sat May 10 16:18:00 2014 -->
+<svg height='69.4444pt' version='1.1' viewBox='182.119 78.0556 104.762 69.4444' width='104.762pt' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
+<defs>
+<path d='M3.03 -3.16H4.73C6.15 -3.16 7.54 -4.2 7.54 -5.32C7.54 -6.09 6.88 -6.83 5.57 -6.83H2.33C2.14 -6.83 2.03 -6.83 2.03 -6.64C2.03 -6.52 2.12 -6.52 2.32 -6.52C2.45 -6.52 2.63 -6.51 2.75 -6.5C2.91 -6.48 2.97 -6.45 2.97 -6.34C2.97 -6.3 2.96 -6.27 2.93 -6.15L1.59 -0.78C1.49 -0.39 1.47 -0.31 0.68 -0.31C0.51 -0.31 0.4 -0.31 0.4 -0.12C0.4 0 0.52 0 0.55 0C0.83 0 1.54 -0.03 1.82 -0.03C2.03 -0.03 2.25 -0.02 2.46 -0.02C2.68 -0.02 2.9 0 3.11 0C3.18 0 3.31 0 3.31 -0.2C3.31 -0.31 3.22 -0.31 3.03 -0.31C2.66 -0.31 2.38 -0.31 2.38 -0.49C2.38 -0.55 2.4 -0.6 2.41 -0.66L3.03 -3.16ZM3.75 -6.14C3.84 -6.49 3.86 -6.52 4.29 -6.52H5.25C6.08 -6.52 6.61 -6.25 6.61 -5.56C6.61 -5.17 6.41 -4.31 6.02 -3.95C5.52 -3.5 4.92 -3.42 4.48 -3.42H3.07L3.75 -6.14Z' id='g1-80'/>
+<path d='M4.38 -0.06C5.93 -0.65 7.4 -2.43 7.4 -4.36C7.4 -5.97 6.34 -7.05 4.85 -7.05C2.69 -7.05 0.49 -4.78 0.49 -2.45C0.49 -0.79 1.61 0.22 3.05 0.22C3.3 0.22 3.64 0.18 4.03 0.07C3.99 0.69 3.99 0.71 3.99 0.84C3.99 1.16 3.99 1.94 4.82 1.94C6.01 1.94 6.49 0.11 6.49 0C6.49 -0.07 6.42 -0.1 6.38 -0.1C6.3 -0.1 6.28 -0.05 6.26 0.01C6.02 0.72 5.44 0.97 5.09 0.97C4.63 0.97 4.48 0.7 4.38 -0.06ZM2.49 -0.14C1.71 -0.45 1.37 -1.23 1.37 -2.13C1.37 -2.82 1.63 -4.24 2.39 -5.32C3.12 -6.34 4.06 -6.8 4.79 -6.8C5.79 -6.8 6.52 -6.02 6.52 -4.68C6.52 -3.68 6.01 -1.34 4.33 -0.4C4.28 -0.75 4.18 -1.48 3.45 -1.48C2.92 -1.48 2.43 -0.98 2.43 -0.46C2.43 -0.26 2.49 -0.15 2.49 -0.14ZM3.11 -0.03C2.97 -0.03 2.65 -0.03 2.65 -0.46C2.65 -0.86 3.03 -1.26 3.45 -1.26S4.06 -1.02 4.06 -0.41C4.06 -0.26 4.05 -0.25 3.95 -0.21C3.69 -0.1 3.39 -0.03 3.11 -0.03Z' id='g1-81'/>
+<path d='M8.34 -2.3C7.79 -1.88 7.52 -1.47 7.44 -1.34C6.99 -0.65 6.91 -0.02 6.91 -0.01C6.91 0.11 7.03 0.11 7.11 0.11C7.28 0.11 7.29 0.09 7.33 -0.09C7.56 -1.07 8.15 -1.91 9.28 -2.37C9.4 -2.41 9.43 -2.43 9.43 -2.5S9.37 -2.6 9.35 -2.61C8.91 -2.78 7.7 -3.28 7.32 -4.96C7.29 -5.08 7.28 -5.11 7.11 -5.11C7.03 -5.11 6.91 -5.11 6.91 -4.99C6.91 -4.97 7 -4.34 7.42 -3.67C7.62 -3.37 7.91 -3.02 8.34 -2.7H0.91C0.73 -2.7 0.55 -2.7 0.55 -2.5S0.73 -2.3 0.91 -2.3H8.34Z' id='g0-33'/>
+<path d='M6.11 -3.2C6.11 -3.53 6.08 -3.56 5.76 -3.56H0.9C0.73 -3.56 0.55 -3.56 0.55 -3.36S0.73 -3.16 0.9 -3.16H5.71V-1.25C5.71 -1.07 5.71 -0.89 5.91 -0.89S6.11 -1.07 6.11 -1.25V-3.2Z' id='g0-58'/>
+<path d='M4.09 -6.28H6.88C7.05 -6.28 7.23 -6.28 7.23 -6.48S7.05 -6.68 6.88 -6.68H0.91C0.76 -6.68 0.55 -6.68 0.55 -6.48S0.76 -6.28 0.91 -6.28H3.69V-0.36C3.69 -0.21 3.69 0 3.89 0S4.09 -0.18 4.09 -0.36V-6.28Z' id='g0-62'/>
+<path d='M4.09 -6.32C4.09 -6.5 4.09 -6.68 3.89 -6.68S3.69 -6.47 3.69 -6.32V-0.4H0.91C0.76 -0.4 0.55 -0.4 0.55 -0.2S0.76 0 0.91 0H6.88C7.05 0 7.23 0 7.23 -0.2S7.05 -0.4 6.88 -0.4H4.09V-6.32Z' id='g0-63'/>
+<path d='M3.56 -5.77C3.48 -5.94 3.42 -5.98 3.33 -5.98C3.2 -5.98 3.17 -5.91 3.11 -5.77L0.62 -0.18C0.56 -0.05 0.55 -0.03 0.55 0.02C0.55 0.13 0.64 0.22 0.75 0.22C0.82 0.22 0.9 0.2 0.98 0.01L3.33 -5.29L5.68 0.01C5.77 0.22 5.87 0.22 5.91 0.22C6.02 0.22 6.11 0.13 6.11 0.02C6.11 0 6.11 -0.02 6.05 -0.14L3.56 -5.77Z' id='g0-94'/>
+<path d='M6.04 -5.58C6.1 -5.71 6.11 -5.73 6.11 -5.78C6.11 -5.89 6.02 -5.98 5.91 -5.98C5.81 -5.98 5.75 -5.91 5.68 -5.76L3.33 -0.47L0.98 -5.77C0.9 -5.96 0.82 -5.98 0.75 -5.98C0.64 -5.98 0.55 -5.89 0.55 -5.78C0.55 -5.76 0.55 -5.74 0.61 -5.62L3.1 0.01C3.18 0.18 3.24 0.22 3.33 0.22C3.46 0.22 3.49 0.15 3.55 0.01L6.04 -5.58Z' id='g0-95'/>
+<path d='M3.31 2.4C3.31 2.37 3.31 2.35 3.14 2.18C1.89 0.92 1.57 -0.97 1.57 -2.5C1.57 -4.24 1.95 -5.98 3.18 -7.23C3.31 -7.35 3.31 -7.37 3.31 -7.4C3.31 -7.47 3.27 -7.5 3.21 -7.5C3.11 -7.5 2.21 -6.82 1.62 -5.55C1.11 -4.45 0.99 -3.34 0.99 -2.5C0.99 -1.72 1.1 -0.51 1.65 0.62C2.25 1.85 3.11 2.5 3.21 2.5C3.27 2.5 3.31 2.47 3.31 2.4Z' id='g2-40'/>
+<path d='M2.89 -2.5C2.89 -3.28 2.78 -4.49 2.23 -5.62C1.63 -6.85 0.77 -7.5 0.67 -7.5C0.61 -7.5 0.57 -7.46 0.57 -7.4C0.57 -7.37 0.57 -7.35 0.76 -7.17C1.74 -6.18 2.31 -4.59 2.31 -2.5C2.31 -0.79 1.94 0.97 0.7 2.23C0.57 2.35 0.57 2.37 0.57 2.4C0.57 2.46 0.61 2.5 0.67 2.5C0.77 2.5 1.67 1.82 2.26 0.55C2.77 -0.55 2.89 -1.66 2.89 -2.5Z' id='g2-41'/>
+</defs>
+<g id='page1'>
+<use x='215.611' xlink:href='#g0-62' y='85'/>
+<use x='226.167' xlink:href='#g0-33' y='85'/>
+<use x='238.944' xlink:href='#g0-63' y='85'/>
+<use x='215.611' xlink:href='#g0-63' y='100'/>
+<use x='226.167' xlink:href='#g0-33' y='100'/>
+<use x='238.944' xlink:href='#g0-62' y='100'/>
+<use x='202.247' xlink:href='#g0-58' y='115'/>
+<use x='208.913' xlink:href='#g0-58' y='115'/>
+<use x='215.58' xlink:href='#g1-80' y='115'/>
+<use x='226.167' xlink:href='#g0-33' y='115'/>
+<use x='238.944' xlink:href='#g1-80' y='115'/>
+<use x='182.119' xlink:href='#g0-58' y='130'/>
+<use x='188.786' xlink:href='#g2-40' y='130'/>
+<use x='192.674' xlink:href='#g1-80' y='130'/>
+<use x='202.706' xlink:href='#g0-95' y='130'/>
+<use x='211.594' xlink:href='#g1-81' y='130'/>
+<use x='219.5' xlink:href='#g2-41' y='130'/>
+<use x='226.167' xlink:href='#g0-33' y='130'/>
+<use x='238.944' xlink:href='#g2-40' y='130'/>
+<use x='242.833' xlink:href='#g0-58' y='130'/>
+<use x='249.5' xlink:href='#g1-80' y='130'/>
+<use x='259.531' xlink:href='#g0-94' y='130'/>
+<use x='268.42' xlink:href='#g0-58' y='130'/>
+<use x='275.087' xlink:href='#g1-81' y='130'/>
+<use x='282.992' xlink:href='#g2-41' y='130'/>
+<use x='182.119' xlink:href='#g0-58' y='145'/>
+<use x='188.786' xlink:href='#g2-40' y='145'/>
+<use x='192.674' xlink:href='#g1-80' y='145'/>
+<use x='202.706' xlink:href='#g0-94' y='145'/>
+<use x='211.594' xlink:href='#g1-81' y='145'/>
+<use x='219.5' xlink:href='#g2-41' y='145'/>
+<use x='226.167' xlink:href='#g0-33' y='145'/>
+<use x='238.944' xlink:href='#g2-40' y='145'/>
+<use x='242.833' xlink:href='#g0-58' y='145'/>
+<use x='249.5' xlink:href='#g1-80' y='145'/>
+<use x='259.531' xlink:href='#g0-95' y='145'/>
+<use x='268.42' xlink:href='#g0-58' y='145'/>
+<use x='275.087' xlink:href='#g1-81' y='145'/>
+<use x='282.992' xlink:href='#g2-41' y='145'/>
+</g>
+</svg>
diff --git a/src/Data/Algebra/Boolean/CNF.hs b/src/Data/Algebra/Boolean/CNF.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/CNF.hs
@@ -0,0 +1,15 @@
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+-- This module re-exports 'Set'-based implementation.
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.CNF (
+  module Data.Algebra.Boolean.CNF.Set
+) where
+
+import Data.Algebra.Boolean.CNF.Set
diff --git a/src/Data/Algebra/Boolean/CNF/List.hs b/src/Data/Algebra/Boolean/CNF/List.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/CNF/List.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.CNF.List (
+  CNF(..),
+  fromDoubleList,
+  toDoubleList,
+  fromNNF,
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Prelude hiding ((||),(&&),not,any,all,and,or)
+import Data.Monoid
+import Data.Either (partitionEithers)
+import Data.Typeable
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Data.Algebra.Boolean.NNF.Tree
+
+import Data.Algebra.Boolean
+
+-- | Boolean formula in Conjunction Normal Form
+--
+-- <<doc-formulae/cnf.svg>>
+newtype CNF a = CNF { unCNF :: [[a]] }
+  deriving (Eq, Ord, Show, Read, Functor, Typeable)
+
+instance CoBoolean1 CNF where
+  toBooleanWith f = all (any f) . unCNF
+
+instance CoBoolean a => CoBoolean (CNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+fromDoubleList :: [[a]] -> CNF a
+fromDoubleList = CNF
+
+toDoubleList :: CNF a -> [[a]]
+toDoubleList = unCNF
+
+cnfNot :: Negable a => CNF a -> CNF a
+cnfNot = any and . map (map $ toNormalForm . Negable.not) . unCNF
+
+instance Negable a => Negable (CNF a) where
+  not                 = cnfNot
+
+instance Negable a => Boolean (CNF a) where
+  false               = CNF [[]]
+  true                = CNF []
+  (CNF a) && (CNF b)  = CNF (a <> b)
+  (CNF a) || (CNF b)  = CNF [ a' <> b' | a' <- a, b' <- b ]
+  not                 = cnfNot
+
+fromNNF :: Negable a => NNF a -> CNF a
+fromNNF = toBooleanWith toNormalForm
+
+instance NormalForm CNF where
+  type NFConstraint CNF a  = Negable a
+
+  toNormalForm x = CNF [[x]]
+
+  simplify f = CNF . p . g . map (h . map f') . unCNF
+    where f' x   = case f x of
+                     Just b   -> Right b
+                     Nothing  -> Left x
+          h :: [Either a Bool] -> Either [a] Bool
+          h disj | True `elem` r   = Right True
+                 | null l          = Right False
+                 | otherwise       = Left l
+            where (l, r)  = partitionEithers disj
+          g :: [Either a Bool] -> Either [a] Bool
+          g conj | False `elem` r  = Right False
+                 | null l          = Right True
+                 | otherwise       = Left l
+            where (l, r)  = partitionEithers conj
+          p :: Either [[a]] Bool -> [[a]]
+          p (Right True)           = []
+          p (Right False)          = [[]]
+          p (Left x)               = x
+
+  fromFreeBoolean = fromNNF . toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/CNF/Set.hs b/src/Data/Algebra/Boolean/CNF/Set.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/CNF/Set.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.CNF.Set (
+  CNF(..),
+  fromDoubleList,
+  toDoubleList,
+  fromNNF,
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Prelude hiding ((||),(&&),not,and,or,any,all)
+import Data.Monoid
+import Data.Typeable
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+
+import Data.List (sortBy, foldl')
+import Data.Function (on)
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Data.Algebra.Boolean.NNF.Tree
+
+import Data.Algebra.Boolean
+
+-- | Boolean formula in Conjunction Normal Form
+--
+-- <<doc-formulae/cnf.svg>>
+newtype CNF a = CNF { unCNF :: Set (Set a) }
+  deriving (Eq, Ord, Show, Read, Typeable)
+
+instance CoBoolean1 CNF where
+  toBooleanWith f = all (any f) . unCNF
+
+instance CoBoolean a => CoBoolean (CNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+toDoubleList :: CNF a -> [[a]]
+toDoubleList = map Set.toList . Set.toList . unCNF
+
+fromDoubleList :: (Ord a) => [[a]] -> CNF a
+fromDoubleList = CNF . Set.fromList . map Set.fromList
+
+cnfNot :: (Ord a, Negable a) => CNF a -> CNF a
+cnfNot = any and . map (map $ toNormalForm . Negable.not) . toDoubleList
+
+instance (Ord a, Negable a) => Negable (CNF a) where
+  not                 = cnfNot
+
+instance (Ord a, Negable a) => Boolean (CNF a) where
+  false               = CNF $ Set.singleton Set.empty
+  true                = CNF Set.empty
+  (CNF a) && (CNF b)  = CNF (a <> b)
+  (CNF a) || (CNF b)  = CNF $ Set.fromList [a' <> b' | a' <- Set.toList a, b' <- Set.toList b ]
+  not                 = cnfNot
+
+fromLeft :: Either a b -> a
+fromLeft (Left x) = x
+fromLeft _        = error "fromLeft called on Right value"
+
+optimize :: Ord a => Set (Set a) -> Set (Set a)
+optimize = Set.fromList . f . sortBy (flip compare `on` Set.size) . Set.toList
+  where f disj = foldl' g disj disj
+        g disj item = filter (not . Set.isProperSubsetOf item) disj
+
+fromNNF :: (Ord a, Negable a) => NNF a -> CNF a
+fromNNF = toBooleanWith toNormalForm
+
+instance NormalForm CNF where
+  type NFConstraint CNF a = (Negable a, Ord a)
+
+  toNormalForm = CNF . Set.singleton . Set.singleton
+
+  simplify f = CNF . optimize . p . g . Set.map h . Set.map (Set.map f') . unCNF
+    where f' x   = case f x of
+                     Just b   -> Right b
+                     Nothing  -> Left x
+          h :: Ord a => Set (Either a Bool) -> Either (Set a) Bool
+          h disj | Right True `Set.member` disj   = Right True
+                 | Set.null l                     = Right False
+                 | otherwise                      = Left l
+            where l  = Set.mapMonotonic fromLeft $ fst $ Set.split (Right minBound) disj
+          g :: Ord a => Set (Either a Bool) -> Either (Set a) Bool
+          g conj | Right False `Set.member` conj  = Right False
+                 | Set.null l                     = Right True
+                 | otherwise                      = Left l
+            where l  = Set.mapMonotonic fromLeft $ fst $ Set.split (Right minBound) conj
+          p :: Either (Set (Set a)) Bool -> Set (Set a)
+          p (Right True)                          = Set.empty
+          p (Right False)                         = Set.singleton Set.empty
+          p (Left x)                              = x
+
+  fromFreeBoolean = fromNNF . toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/CoBoolean.hs b/src/Data/Algebra/Boolean/CoBoolean.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/CoBoolean.hs
@@ -0,0 +1,50 @@
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.CoBoolean (
+  CoBoolean(..),
+  CoBoolean1(..),
+  toBool,
+  toBoolWith
+) where
+
+import Data.Algebra.Boolean
+
+-- | A class to values translable to booleans.
+class CoBoolean a where
+  -- | Cast value to 'Boolean'.
+  toBoolean :: Boolean b => a -> b
+
+-- | Less polymorphic version of 'toBoolean'.
+toBool :: CoBoolean a => a -> Bool
+toBool = toBoolean
+
+-- | A polymorphic class of values translable to booleans.
+class CoBoolean1 b where
+  -- | Cast value to 'Boolean'.
+  toBooleanWith :: Boolean c => (a -> c) -> b a -> c
+
+-- | Less polymorphic version of 'toBooleanWith'.
+toBoolWith :: CoBoolean1 b => (a -> Bool) -> b a -> Bool
+toBoolWith = toBooleanWith
+
+instance CoBoolean Bool where
+  toBoolean = fromBool
+
+instance (CoBoolean f, CoBoolean g) => CoBoolean (Either f g) where
+  toBoolean (Left x)   = toBoolean x
+  toBoolean (Right x)  = toBoolean x
+
+instance CoBoolean (Maybe a) where
+  toBoolean (Just _) = true
+  toBoolean Nothing  = false
+
+instance CoBoolean1 Maybe where
+  toBooleanWith f (Just x) = f x
+  toBooleanWith _ Nothing  = false
diff --git a/src/Data/Algebra/Boolean/DNF.hs b/src/Data/Algebra/Boolean/DNF.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/DNF.hs
@@ -0,0 +1,15 @@
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+-- This module re-exports 'Set'-based implementation.
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.DNF (
+  module Data.Algebra.Boolean.DNF.Set
+) where
+
+import Data.Algebra.Boolean.DNF.Set
diff --git a/src/Data/Algebra/Boolean/DNF/List.hs b/src/Data/Algebra/Boolean/DNF/List.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/DNF/List.hs
@@ -0,0 +1,92 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.DNF.List (
+  DNF(..),
+  fromDoubleList,
+  toDoubleList,
+  fromNNF,
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Prelude hiding ((||),(&&),not,any,all,and,or)
+import Data.Monoid
+import Data.Either (partitionEithers)
+import Data.Typeable
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Data.Algebra.Boolean.NNF.Tree
+
+import Data.Algebra.Boolean
+
+-- | Boolean formula in Disjunction Normal Form
+--
+-- <<doc-formulae/dnf.svg>>
+newtype DNF a = DNF { unDNF :: [[a]] }
+  deriving (Eq, Ord, Show, Read, Functor, Typeable)
+
+instance CoBoolean1 DNF where
+  toBooleanWith f = any (all f) . unDNF
+
+instance CoBoolean a => CoBoolean (DNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+fromDoubleList :: [[a]] -> DNF a
+fromDoubleList = DNF
+
+toDoubleList :: DNF a -> [[a]]
+toDoubleList = unDNF
+
+dnfNot :: Negable a => DNF a -> DNF a
+dnfNot = all or . map (map $ toNormalForm . Negable.not) . unDNF
+
+instance Negable a => Negable (DNF a) where
+  not                 = dnfNot
+
+instance Negable a => Boolean (DNF a) where
+  true                = DNF [[]]
+  false               = DNF []
+  (DNF a) || (DNF b)  = DNF (a <> b)
+  (DNF a) && (DNF b)  = DNF [ a' <> b' | a' <- a, b' <- b ]
+  not                 = dnfNot
+
+fromNNF :: Negable a => NNF a -> DNF a
+fromNNF = toBooleanWith toNormalForm
+
+instance NormalForm DNF where
+  type NFConstraint DNF a  = Negable a
+
+  toNormalForm x = DNF [[x]]
+
+  simplify f = DNF . q . h . map (g . map f') . unDNF
+    where f' x   = case f x of
+                     Just b   -> Right b
+                     Nothing  -> Left x
+          h :: [Either a Bool] -> Either [a] Bool
+          h disj | True `elem` r   = Right True
+                 | null l          = Right False
+                 | otherwise       = Left l
+            where (l, r)  = partitionEithers disj
+          g :: [Either a Bool] -> Either [a] Bool
+          g conj | False `elem` r  = Right False
+                 | null l          = Right True
+                 | otherwise       = Left l
+            where (l, r)  = partitionEithers conj
+          q :: Either [[a]] Bool -> [[a]]
+          q (Right True)           = [[]]
+          q (Right False)          = []
+          q (Left x)               = x
+
+  fromFreeBoolean = fromNNF . toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/DNF/Set.hs b/src/Data/Algebra/Boolean/DNF/Set.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/DNF/Set.hs
@@ -0,0 +1,105 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.DNF.Set (
+  DNF(..),
+  fromDoubleList,
+  toDoubleList,
+  fromNNF,
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Prelude hiding ((||),(&&),not,and,or,any,all)
+import Data.Monoid
+import Data.Typeable
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+
+import Data.List (sortBy, foldl')
+import Data.Function (on)
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Data.Algebra.Boolean.NNF.Tree
+
+import Data.Algebra.Boolean
+
+-- | Boolean formula in Disjunction Normal Form
+--
+-- <<doc-formulae/dnf.svg>>
+newtype DNF a = DNF { unDNF :: Set (Set a) }
+  deriving (Eq, Ord, Show, Read, Typeable)
+
+instance CoBoolean1 DNF where
+  toBooleanWith f = any (all f) . unDNF
+
+instance CoBoolean a => CoBoolean (DNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+toDoubleList :: DNF a -> [[a]]
+toDoubleList = map Set.toList . Set.toList . unDNF
+
+fromDoubleList :: (Ord a) => [[a]] -> DNF a
+fromDoubleList = DNF . Set.fromList . map Set.fromList
+
+dnfNot :: (Ord a, Negable a) => DNF a -> DNF a
+dnfNot = all or . map (map $ toNormalForm . Negable.not) . toDoubleList
+
+instance (Ord a, Negable a) => Negable (DNF a) where
+  not                 = dnfNot
+
+instance (Ord a, Negable a) => Boolean (DNF a) where
+  true                = DNF $ Set.singleton Set.empty
+  false               = DNF Set.empty
+  (DNF a) || (DNF b)  = DNF (a <> b)
+  (DNF a) && (DNF b)  = DNF $ Set.fromList [a' <> b' | a' <- Set.toList a, b' <- Set.toList b ]
+  not                 = dnfNot
+
+fromLeft :: Either a b -> a
+fromLeft (Left x) = x
+fromLeft _        = error "fromLeft called on Right value"
+
+optimize :: Ord a => Set (Set a) -> Set (Set a)
+optimize = Set.fromList . f . sortBy (compare `on` Set.size) . Set.toList
+  where f conj = foldl' g conj conj
+        g conj item = filter (not . Set.isProperSubsetOf item) conj
+
+fromNNF :: (Ord a, Negable a) => NNF a -> DNF a
+fromNNF = toBooleanWith toNormalForm
+
+instance NormalForm DNF where
+  type NFConstraint DNF a = (Negable a, Ord a)
+
+  toNormalForm = DNF . Set.singleton . Set.singleton
+
+  simplify f = DNF . optimize . q . h . Set.map g . Set.map (Set.map f') . unDNF
+    where f' x   = case f x of
+                     Just b   -> Right b
+                     Nothing  -> Left x
+          h :: Ord a => Set (Either a Bool) -> Either (Set a) Bool
+          h disj | Right True `Set.member` disj   = Right True
+                 | Set.null l                     = Right False
+                 | otherwise                      = Left l
+            where l  = Set.mapMonotonic fromLeft $ fst $ Set.split (Right minBound) disj
+          g :: Ord a => Set (Either a Bool) -> Either (Set a) Bool
+          g conj | Right False `Set.member` conj  = Right False
+                 | Set.null l                     = Right True
+                 | otherwise                      = Left l
+            where l  = Set.mapMonotonic fromLeft $ fst $ Set.split (Right minBound) conj
+          q :: Either (Set (Set a)) Bool -> Set (Set a)
+          q (Right True)                          = Set.singleton Set.empty
+          q (Right False)                         = Set.empty
+          q (Left x)                              = x
+
+  fromFreeBoolean = fromNNF . toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/FreeBoolean.hs b/src/Data/Algebra/Boolean/FreeBoolean.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/FreeBoolean.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.FreeBoolean (
+  FreeBoolean(..),
+  module Data.Algebra.Boolean.CoBoolean
+) where
+
+import Data.Typeable
+
+import Data.Algebra.Boolean.CoBoolean
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Prelude hiding ((||),(&&),not,any,all)
+import Data.Algebra.Boolean
+
+-- | Free 'Boolean' type, does not perform any optimizations on the structure. Useful only in tests.
+--
+-- Consider using 'Data.Algebra.Boolean.NNF'.
+data FreeBoolean a = FBValue a
+                   | FBTrue
+                   | FBFalse
+                   | FBNot (FreeBoolean a)
+                   | FBAnd (FreeBoolean a) (FreeBoolean a)
+                   | FBOr (FreeBoolean a) (FreeBoolean a)
+  deriving (Eq, Ord, Show, Read, Functor, Typeable)
+
+instance CoBoolean1 FreeBoolean where
+  toBooleanWith f (FBValue x)  = f x
+  toBooleanWith _ FBTrue       = true
+  toBooleanWith _ FBFalse      = false
+  toBooleanWith f (FBNot x)    = not $ toBooleanWith f x
+  toBooleanWith f (FBOr a b)   = toBooleanWith f a || toBooleanWith f b
+  toBooleanWith f (FBAnd a b)  = toBooleanWith f a && toBooleanWith f b
+
+instance CoBoolean a => CoBoolean (FreeBoolean a) where
+  toBoolean = toBooleanWith toBoolean
+
+instance Negable (FreeBoolean a) where
+  not = FBNot
+
+instance Boolean (FreeBoolean a) where
+  true   = FBTrue
+  false  = FBFalse
+  (||)   = FBOr
+  (&&)   = FBAnd
+  not    = FBNot
diff --git a/src/Data/Algebra/Boolean/NNF.hs b/src/Data/Algebra/Boolean/NNF.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/NNF.hs
@@ -0,0 +1,15 @@
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+-- This module re-exports tree-based implementation.
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.NNF (
+  module Data.Algebra.Boolean.NNF.Tree
+) where
+
+import Data.Algebra.Boolean.NNF.Tree
diff --git a/src/Data/Algebra/Boolean/NNF/Set.hs b/src/Data/Algebra/Boolean/NNF/Set.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/NNF/Set.hs
@@ -0,0 +1,102 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.NNF.Set (
+  NNF(..),
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Prelude hiding ((||),(&&),not,any,all)
+import Data.Algebra.Boolean
+
+import Data.Typeable
+
+-- | Boolean formula in Negation Normal Form
+--
+-- 'Boolean' operations will perform transformations as below:
+--
+-- <<doc-formulae/nnf.svg>>
+data NNF a = NNFTrue
+           | NNFFalse
+           | NNFValue a
+           | NNFOr (Set (NNF a))
+           | NNFAnd (Set (NNF a))
+  deriving (Eq, Ord, Show, Read, Typeable)
+
+instance CoBoolean a => CoBoolean (NNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+instance CoBoolean1 NNF where
+  toBooleanWith _ NNFTrue       = true
+  toBooleanWith _ NNFFalse      = false
+  toBooleanWith f (NNFValue x)  = f x
+  toBooleanWith f (NNFOr xs)    = any (toBooleanWith f) xs
+  toBooleanWith f (NNFAnd xs)   = all (toBooleanWith f) xs
+
+orList :: Ord a => [NNF a] -> NNF a
+orList x
+  | Set.null s             = NNFFalse
+  | Set.member NNFTrue s   = NNFTrue
+  | otherwise              = NNFOr s
+  where s = Set.fromList $ filter (/= NNFFalse) $ concatMap g x
+        g (NNFOr xs)   = concatMap g $ Set.toList xs
+        g y            = [y]
+
+andList :: Ord a => [NNF a] -> NNF a
+andList x
+  | Set.null s             = NNFTrue
+  | Set.member NNFFalse s  = NNFFalse
+  | otherwise              = NNFAnd s
+  where s = Set.fromList $ filter (/= NNFTrue) $ concatMap g x
+        g (NNFAnd xs)  = concatMap g $ Set.toList xs
+        g y            = [y]
+
+nnfNot :: (Negable a, Ord a) => NNF a -> NNF a
+nnfNot NNFTrue       = NNFFalse
+nnfNot NNFFalse      = NNFTrue
+nnfNot (NNFValue x)  = NNFValue $ Negable.not x
+nnfNot (NNFOr xs)    = NNFAnd $ Set.map not xs
+nnfNot (NNFAnd xs)   = NNFOr $ Set.map not xs
+
+instance (Ord a, Negable a) => Negable (NNF a) where
+  not     = nnfNot
+
+instance (Ord a, Negable a) => Boolean (NNF a) where
+  true    = NNFTrue
+  false   = NNFFalse
+
+  a || b  = orList [a, b]
+  a && b  = andList [a, b]
+
+  not     = nnfNot
+
+instance NormalForm NNF where
+  type NFConstraint NNF a  = (Ord a, Negable a)
+
+  toNormalForm             = NNFValue
+
+  simplify f (NNFValue x)  = case f x of
+                               Just True   -> NNFTrue
+                               Just False  -> NNFFalse
+                               Nothing     -> NNFValue x
+  simplify _ NNFTrue       = NNFTrue
+  simplify _ NNFFalse      = NNFFalse
+  simplify f (NNFAnd xs)   = andList $ map (simplify f) $ Set.toList xs
+  simplify f (NNFOr xs)    = orList $ map (simplify f) $ Set.toList xs
+
+  fromFreeBoolean          = toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/NNF/Tree.hs b/src/Data/Algebra/Boolean/NNF/Tree.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/NNF/Tree.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.NNF.Tree (
+  NNF(..),
+  module Data.Algebra.Boolean.NormalForm
+  ) where
+
+import Prelude hiding ((||),(&&),not)
+
+import Data.Algebra.Boolean.NormalForm
+import Data.Algebra.Boolean.Negable hiding (not)
+import qualified Data.Algebra.Boolean.Negable as Negable
+
+import Data.Algebra.Boolean
+
+import Data.Typeable
+
+-- | Boolean formula in Negation Normal Form
+--
+-- 'Boolean' operations will perform transformations as below:
+--
+-- <<doc-formulae/nnf.svg>>
+data NNF a = NNFTrue
+           | NNFFalse
+           | NNFValue a
+           | NNFOr (NNF a) (NNF a)
+           | NNFAnd (NNF a) (NNF a)
+  deriving (Eq, Ord, Show, Read, Functor, Typeable)
+
+instance CoBoolean a => CoBoolean (NNF a) where
+  toBoolean = toBooleanWith toBoolean
+
+instance CoBoolean1 NNF where
+  toBooleanWith _ NNFTrue       = true
+  toBooleanWith _ NNFFalse      = false
+  toBooleanWith f (NNFValue x)  = f x
+  toBooleanWith f (NNFOr a b)   = toBooleanWith f a || toBooleanWith f b
+  toBooleanWith f (NNFAnd a b)  = toBooleanWith f a && toBooleanWith f b
+
+nnfNot :: Negable a => NNF a -> NNF a
+nnfNot (NNFTrue)     = NNFFalse
+nnfNot NNFFalse      = NNFTrue
+nnfNot (NNFValue x)  = NNFValue $ Negable.not x
+nnfNot (NNFOr a b)   = NNFAnd (not a) (not b)
+nnfNot (NNFAnd a b)  = NNFOr (not a) (not b)
+
+nnfOr :: NNF a -> NNF a -> NNF a
+nnfOr  NNFTrue   _         = NNFTrue
+nnfOr  _         NNFTrue   = NNFTrue
+nnfOr  NNFFalse  NNFFalse  = NNFFalse
+nnfOr  a         b         = NNFOr a b
+
+nnfAnd :: NNF a -> NNF a -> NNF a
+nnfAnd NNFFalse  _         = NNFFalse
+nnfAnd _         NNFFalse  = NNFFalse
+nnfAnd NNFTrue   NNFTrue   = NNFTrue
+nnfAnd a         b         = NNFAnd a b
+
+instance Negable a => Negable (NNF a) where
+  not    = nnfNot
+
+instance Negable a => Boolean (NNF a) where
+  true   = NNFTrue
+  false  = NNFFalse
+  (||)   = nnfOr
+  (&&)   = nnfAnd
+  not    = nnfNot
+
+instance NormalForm NNF where
+  type NFConstraint NNF a  = Negable a
+
+  toNormalForm             = NNFValue
+
+  simplify f (NNFValue x)  = case f x of
+                               Just True   -> NNFTrue
+                               Just False  -> NNFFalse
+                               Nothing     -> NNFValue x
+  simplify _ NNFTrue       = NNFTrue
+  simplify _ NNFFalse      = NNFFalse
+  simplify f (NNFAnd a b)  = nnfAnd (simplify f a) (simplify f b)
+  simplify f (NNFOr a b)   = nnfOr (simplify f a) (simplify f b)
+
+  fromFreeBoolean          = toBooleanWith toNormalForm
diff --git a/src/Data/Algebra/Boolean/Negable.hs b/src/Data/Algebra/Boolean/Negable.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/Negable.hs
@@ -0,0 +1,67 @@
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.Negable (
+  Neg(..),
+  Negable(..)
+)
+  where
+
+import Data.Algebra.Boolean.CoBoolean
+
+import Data.Monoid
+import Data.Typeable
+
+import Prelude hiding (not)
+import qualified Prelude as P
+
+import qualified Data.Algebra.Boolean as B
+
+-- | Free 'Negable'.
+data Neg a = Pos a -- ^ Positive value
+           | Neg a -- ^ Negative value
+  deriving (Eq, Ord, Show, Read, Functor, Typeable)
+
+instance CoBoolean (Neg a) where
+  toBoolean (Pos _) = B.true
+  toBoolean (Neg _) = B.false
+
+instance CoBoolean1 Neg where
+  toBooleanWith f (Pos x) = f x
+  toBooleanWith f (Neg x) = B.not $ f x
+
+-- | Class to represent invertible values.
+--
+-- Must obey the double negation law: @not (not x) = x@
+--
+-- The negation appears only in leafs of normal forms, and the underlying proposition class
+-- might have built-in negation, thus we use 'Negable'.
+class Negable x where
+  -- | Invert the value.
+  not :: x -> x
+
+instance Negable (Neg a) where
+  not (Pos x)  = Neg x
+  not (Neg x)  = Pos x
+
+instance Negable Bool where
+  not = P.not
+
+instance Monoid m => Negable (Maybe m) where
+  not (Just _)  = Nothing
+  not Nothing   = Just mempty
+
+instance (Negable a, Negable b) => Negable (a, b) where
+  not (x, y) = (not x, not y)
+
+instance (Negable a, Negable b) => Negable (Either a b) where
+  not (Left x)  = Left $ not x
+  not (Right y) = Right $ not y
diff --git a/src/Data/Algebra/Boolean/NormalForm.hs b/src/Data/Algebra/Boolean/NormalForm.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/Algebra/Boolean/NormalForm.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+--------------------------------------------------------------------
+-- |
+-- Copyright :  © Oleg Grenrus 2014
+-- License   :  MIT
+-- Maintainer:  Oleg Grenrus <oleg.grenrus@iki.fi>
+-- Stability :  experimental
+-- Portability: non-portable
+--
+--------------------------------------------------------------------
+module Data.Algebra.Boolean.NormalForm (
+  NormalForm(..),
+  module Data.Algebra.Boolean.CoBoolean
+  ) where
+
+import Data.Algebra.Boolean.CoBoolean
+import Data.Algebra.Boolean.FreeBoolean
+
+import GHC.Exts (Constraint)
+
+-- | Class unifying different boolean normal forms.
+class CoBoolean1 nf => NormalForm nf where
+  -- | 'NormalForm' could be constrained, so the 'Set' based implementations could be included.
+  type NFConstraint nf a :: Constraint
+  type NFConstraint nf a = ()
+
+  -- | Lift a value into normal form.
+  toNormalForm :: a -> nf a
+
+  -- | Simplify the formula, if some terms are ⊥ or ⊤.
+  simplify :: (NFConstraint nf a) => (a -> Maybe Bool) -> nf a -> nf a
+
+  -- | transform from free boolean form
+  fromFreeBoolean :: (NFConstraint nf a) => FreeBoolean a -> nf a
+
+instance NormalForm FreeBoolean where
+  toNormalForm     = FBValue
+  simplify _       = id
+  fromFreeBoolean  = id
diff --git a/tests/BooleanLaws.hs b/tests/BooleanLaws.hs
new file mode 100644
--- /dev/null
+++ b/tests/BooleanLaws.hs
@@ -0,0 +1,91 @@
+module BooleanLaws (monotoneLaws, nonMonotoneLaws) where
+
+import Test.Tasty
+import Test.Tasty.QuickCheck as QC
+
+import Prelude hiding ((||),(&&),not)
+import Data.Algebra.Boolean
+
+associativityOrProp :: Boolean a => (a -> a -> Bool) -> a -> a -> a -> Bool
+associativityOrProp eq x y z = (x || (y || z)) `eq` ((x || y) || z)
+
+associativityAndProp :: Boolean a => (a -> a -> Bool) -> a -> a -> a -> Bool
+associativityAndProp eq x y z = (x || (y || z)) `eq` ((x || y) || z)
+
+commutativeOrProp :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+commutativeOrProp eq x y = (x || y) `eq` (y || x)
+
+commutativeAndProp :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+commutativeAndProp eq x y = (x && y) `eq` (y && x)
+
+distributivity1Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> a -> Bool
+distributivity1Prop eq x y z = (x && (y || z)) `eq` ((x && y) || (x && z))
+
+distributivity2Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> a -> Bool
+distributivity2Prop eq x y z = (x || (y && z)) `eq` ((x || y) && (x || z))
+
+identityOrProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+identityOrProp eq x = (x || false) `eq` x
+
+identityAndProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+identityAndProp eq x = (x && true) `eq` x
+
+annihilatorOrProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+annihilatorOrProp eq x = (x || true) `eq` true
+
+annihilatorAndProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+annihilatorAndProp eq x = (x && false) `eq` false
+
+idempotenceOrProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+idempotenceOrProp eq x = (x || x) `eq` x
+
+idempotenceAndProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+idempotenceAndProp eq x = (x && x) `eq` x
+
+absorption1Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+absorption1Prop eq x y = (x && (x || y)) `eq` x
+
+absorption2Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+absorption2Prop eq x y = (x || (x && y)) `eq` x
+
+complementationOrProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+complementationOrProp eq x = (x || not x) `eq` true
+
+complementationAndProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+complementationAndProp eq x = (x && not x) `eq` false
+
+doubleNegationProp :: Boolean a => (a -> a -> Bool) -> a -> Bool
+doubleNegationProp eq x = not (not x) `eq` x
+
+deMorgan1Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+deMorgan1Prop eq x y = (not x && not y) `eq` not (x || y)
+
+deMorgan2Prop :: Boolean a => (a -> a -> Bool) -> a -> a -> Bool
+deMorgan2Prop eq x y = (not x || not y) `eq` not (x && y)
+
+monotoneLaws :: (Arbitrary a, Show a, Boolean a) => (a -> a -> Bool) -> TestTree
+monotoneLaws eq = testGroup "Monotone boolean laws"
+  [ QC.testProperty "associativity or"     $ associativityOrProp eq
+  , QC.testProperty "associativity and"    $ associativityAndProp eq
+  , QC.testProperty "commutativity or"     $ commutativeOrProp eq
+  , QC.testProperty "commutativity and"    $ commutativeAndProp eq
+  , QC.testProperty "distributivity 1"     $ distributivity1Prop eq
+  , QC.testProperty "distributivity 2"     $ distributivity2Prop eq
+  , QC.testProperty "identity or"          $ identityOrProp eq
+  , QC.testProperty "identity and"         $ identityAndProp eq
+  , QC.testProperty "annihilator or"       $ annihilatorOrProp eq
+  , QC.testProperty "annihilator and"      $ annihilatorAndProp eq
+  , QC.testProperty "idempotence or"       $ idempotenceOrProp eq
+  , QC.testProperty "idempotence and"      $ idempotenceAndProp eq
+  , QC.testProperty "absorption 1"         $ absorption1Prop eq
+  , QC.testProperty "absorption 2"         $ absorption2Prop eq
+  ]
+
+nonMonotoneLaws :: (Arbitrary a, Show a, Boolean a) => (a -> a -> Bool) -> TestTree
+nonMonotoneLaws eq = testGroup "Non monotone boolean laws"
+  [ QC.testProperty "complementation or"   $ complementationOrProp eq
+  , QC.testProperty "complementation and"  $ complementationAndProp eq
+  , QC.testProperty "double negation"      $ doubleNegationProp eq
+  , QC.testProperty "de Morgan 1"          $ deMorgan1Prop eq
+  , QC.testProperty "de Morgan 2"          $ deMorgan2Prop eq
+  ]
diff --git a/tests/CNFList.hs b/tests/CNFList.hs
new file mode 100644
--- /dev/null
+++ b/tests/CNFList.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module CNFList (tests) where
+
+import BooleanLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable, Neg(..))
+import Data.Algebra.Boolean.CNF.List
+
+instance Arbitrary a => Arbitrary (Neg a) where
+  arbitrary = oneof [ Pos <$> arbitrary, Pos <$> arbitrary ]
+
+instance (Negable a, Arbitrary a) => Arbitrary (CNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "CNF list implementation"
+  [ monotoneLaws eq
+  , simplifyLaws (undefined :: CNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: CNF (Either Bool Bool))
+  ]
+
+eq :: CNF (Neg Int) -> CNF (Neg Int) -> Bool
+eq = (==) `on` toBool
diff --git a/tests/CNFSet.hs b/tests/CNFSet.hs
new file mode 100644
--- /dev/null
+++ b/tests/CNFSet.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module CNFSet (tests) where
+
+import BooleanLaws
+import NegableLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Monoid
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable)
+import Data.Algebra.Boolean.CNF.Set
+
+instance Arbitrary a => Arbitrary (Sum a) where
+  arbitrary = Sum <$> arbitrary
+
+instance (Ord a, Negable a, Arbitrary a) => Arbitrary (CNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "CNF set implementation"
+  [ monotoneLaws eq
+  , nonMonotoneLaws eq
+  , negableLaws eq
+  , simplifyLaws (undefined :: CNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: CNF (Either Bool Bool))
+  ]
+
+eq :: CNF (Maybe (Sum Int)) -> CNF (Maybe (Sum Int)) -> Bool
+eq = (==) `on` toBool
diff --git a/tests/DNFList.hs b/tests/DNFList.hs
new file mode 100644
--- /dev/null
+++ b/tests/DNFList.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module DNFList (tests) where
+
+import BooleanLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable, Neg(..))
+import Data.Algebra.Boolean.DNF.List
+
+instance Arbitrary a => Arbitrary (Neg a) where
+  arbitrary = oneof [ Pos <$> arbitrary, Pos <$> arbitrary ]
+
+instance (Negable a, Arbitrary a) => Arbitrary (DNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "DNF list implementation"
+  [ monotoneLaws eq
+  , simplifyLaws (undefined :: DNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: DNF (Either Bool Bool))
+  ]
+
+eq :: DNF (Neg Int) -> DNF (Neg Int) -> Bool
+eq = (==) `on` toBool
diff --git a/tests/DNFSet.hs b/tests/DNFSet.hs
new file mode 100644
--- /dev/null
+++ b/tests/DNFSet.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module DNFSet (tests) where
+
+import BooleanLaws
+import NegableLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Monoid
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable)
+import Data.Algebra.Boolean.DNF.Set
+
+instance Arbitrary a => Arbitrary (Sum a) where
+  arbitrary = Sum <$> arbitrary
+
+instance (Ord a, Negable a, Arbitrary a) => Arbitrary (DNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "DNF set implementation"
+  [ monotoneLaws eq
+  , nonMonotoneLaws eq
+  , negableLaws eq
+  , simplifyLaws (undefined :: DNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: DNF (Either Bool Bool))
+  ]
+
+eq :: DNF (Maybe (Sum Int)) -> DNF (Maybe (Sum Int)) -> Bool
+eq = (==) `on` toBool
diff --git a/tests/FreeBoolean.hs b/tests/FreeBoolean.hs
new file mode 100644
--- /dev/null
+++ b/tests/FreeBoolean.hs
@@ -0,0 +1,33 @@
+module FreeBoolean (
+  freeBooleanHeight,
+  module Data.Algebra.Boolean.FreeBoolean
+  ) where
+
+import Test.Tasty.QuickCheck
+
+import Control.Applicative
+
+import Data.Algebra.Boolean.FreeBoolean
+
+instance Arbitrary a => Arbitrary (FreeBoolean a) where
+  arbitrary = sized arbitrary'
+    where arbitrary' 0 = frequency [
+            (10, FBValue <$> arbitrary),
+            (1, pure FBTrue),
+            (1, pure FBFalse)
+            ]
+          arbitrary' n = oneof [
+            FBValue <$> arbitrary,
+            FBNot <$> arbitrary'',
+            FBAnd <$> arbitrary'' <*> arbitrary'',
+            FBOr <$> arbitrary'' <*> arbitrary''
+            ]
+            where arbitrary'' = arbitrary' $ n `div` 2
+
+freeBooleanHeight :: FreeBoolean a -> Int
+freeBooleanHeight FBTrue = 0
+freeBooleanHeight FBFalse = 0
+freeBooleanHeight (FBValue _) = 0
+freeBooleanHeight (FBNot x) = 1 + freeBooleanHeight x
+freeBooleanHeight (FBAnd a b) = 1 + max (freeBooleanHeight a) (freeBooleanHeight b)
+freeBooleanHeight (FBOr a b) = 1 + max (freeBooleanHeight a) (freeBooleanHeight b)
diff --git a/tests/FreeBooleanTests.hs b/tests/FreeBooleanTests.hs
new file mode 100644
--- /dev/null
+++ b/tests/FreeBooleanTests.hs
@@ -0,0 +1,24 @@
+module FreeBooleanTests where
+
+import BooleanLaws
+import NegableLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Data.Function (on)
+
+import Test.Tasty
+
+tests :: TestTree
+tests = testGroup "Free boolean"
+  [ monotoneLaws eq
+  , nonMonotoneLaws eq
+  , negableLaws eq
+  , simplifyLaws (undefined :: FreeBoolean (Either Bool Bool))
+  , booleanModelLaws (undefined :: FreeBoolean (Either Bool Bool))
+  ]
+
+eq :: FreeBoolean Int -> FreeBoolean Int -> Bool
+eq = (==) `on` toBoolWith (1==)
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,25 @@
+module Main (main) where
+
+import qualified NNFTree
+import qualified NNFSet
+import qualified DNFList
+import qualified DNFSet
+import qualified CNFList
+import qualified CNFSet
+import qualified FreeBooleanTests as FBT
+
+import Test.Tasty
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "Tests" [
+  NNFTree.tests,
+  NNFSet.tests,
+  CNFList.tests,
+  CNFSet.tests,
+  DNFList.tests,
+  DNFSet.tests,
+  FBT.tests
+  ]
diff --git a/tests/NNFSet.hs b/tests/NNFSet.hs
new file mode 100644
--- /dev/null
+++ b/tests/NNFSet.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module NNFSet (tests) where
+
+import BooleanLaws
+import NegableLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Monoid
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable)
+import Data.Algebra.Boolean.NNF.Set
+
+instance Arbitrary a => Arbitrary (Sum a) where
+  arbitrary = Sum <$> arbitrary
+
+instance (Ord a, Negable a, Arbitrary a) => Arbitrary (NNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "NNF set implementation"
+  [ monotoneLaws eq
+  , nonMonotoneLaws eq
+  , negableLaws eq
+  , simplifyLaws (undefined :: NNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: NNF (Either Bool Bool))
+  ]
+
+eq :: NNF (Maybe (Sum Int)) -> NNF (Maybe (Sum Int)) -> Bool
+eq = (==) `on` toBool
diff --git a/tests/NNFTree.hs b/tests/NNFTree.hs
new file mode 100644
--- /dev/null
+++ b/tests/NNFTree.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module NNFTree (tests) where
+
+import BooleanLaws
+import NegableLaws
+import SimplifyLaws
+import BooleanModelLaws
+
+import FreeBoolean
+
+import Test.Tasty
+import Test.QuickCheck
+
+import Control.Applicative
+import Data.Function (on)
+
+import Data.Algebra.Boolean.Negable (Negable)
+import Data.Algebra.Boolean.NNF.Tree
+
+instance (Negable a, Arbitrary a) => Arbitrary (NNF a) where
+  arbitrary = fromFreeBoolean <$> arbitrary
+
+tests :: TestTree
+tests = testGroup "NNF tree implementation"
+  [ monotoneLaws eq
+  , nonMonotoneLaws eq
+  , negableLaws eq
+  , simplifyLaws (undefined :: NNF (Either Bool Bool))
+  , booleanModelLaws (undefined :: NNF (Either Bool Bool))
+  ]
+
+eq :: NNF Bool -> NNF Bool -> Bool
+eq = (==) `on` toBool
diff --git a/tests/NegableLaws.hs b/tests/NegableLaws.hs
new file mode 100644
--- /dev/null
+++ b/tests/NegableLaws.hs
@@ -0,0 +1,15 @@
+module NegableLaws (negableLaws) where
+
+import Test.Tasty
+import Test.Tasty.QuickCheck as QC
+
+import Prelude hiding (not)
+import Data.Algebra.Boolean.Negable
+
+doubleNegationProp :: Negable a => (a -> a -> Bool) -> a -> Bool
+doubleNegationProp eq x = not (not x) `eq` x
+
+negableLaws :: (Arbitrary a, Show a, Negable a) => (a -> a -> Bool) -> TestTree
+negableLaws eq = testGroup "Negable laws"
+  [ QC.testProperty "double negation"      $ doubleNegationProp eq
+  ]
diff --git a/tests/SimplifyLaws.hs b/tests/SimplifyLaws.hs
new file mode 100644
--- /dev/null
+++ b/tests/SimplifyLaws.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ConstraintKinds #-}
+module SimplifyLaws (simplifyLaws) where
+
+import Test.Tasty
+import Test.Tasty.QuickCheck as QC
+
+import Data.Algebra.Boolean.CoBoolean
+import Data.Algebra.Boolean.NormalForm
+
+simplifyProperty :: (CoBoolean a, NormalForm nf, NFConstraint nf (Either a Bool)) => nf (Either a Bool) -> Bool
+simplifyProperty nf = toBooleanWith toBoolean (simplify f nf) == (toBooleanWith toBoolean nf :: Bool)
+  where f (Right b)  = Just b
+        f (Left _)   = Nothing
+
+simplifyLaws :: forall a nf. (CoBoolean a, NormalForm nf, NFConstraint nf (Either a Bool), Show (nf (Either a Bool)), Arbitrary (nf (Either a Bool))) => nf (Either a Bool) -> TestTree
+simplifyLaws _ = QC.testProperty "simplify property" prop
+  where prop :: nf (Either a Bool) -> Bool
+        prop = simplifyProperty
