diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## New in 0.1.0.0
+
+* Initial release of the `NFunctor` typeclass
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+# N-ary Functors
+
+[`Functor`](https://hackage.haskell.org/package/base-4.10.1.0/docs/Prelude.html#t:Functor) and [`Bifunctor`](https://hackage.haskell.org/package/base-4.10.1.0/docs/Data-Bifunctor.html#t:Bifunctor) are both in `base`, but what about `Trifunctor`? `Quadrifunctor`? There must be a better solution than creating an infinite tower of typeclasses. Here's the API I managed to implement:
+
+    > nmap <#> (+1) <#> (+2) $ (0, 0)
+    (1,2)
+
+    > nmap <#> (+1) <#> (+2) <#> (+3) $ (0, 0, 0)
+    (1,2,3)
+
+    > nmap <#> (+1) <#> (+2) <#> (+3) <#> (+4) $ (0, 0, 0, 0)
+    (1,2,3,4)
+
+For more details, see the [documentation](https://hackage.haskell.org/package/n-ary-functor-0.1.0.0/docs/NAryFunctor.html) and the [blog post](http://gelisam.blogspot.ca/2017/12/n-ary-functors.html).
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/n-ary-functor.cabal b/n-ary-functor.cabal
new file mode 100644
--- /dev/null
+++ b/n-ary-functor.cabal
@@ -0,0 +1,27 @@
+name:                n-ary-functor
+version:             0.1.0.0
+synopsis:            An n-ary version of Functor
+description:         A single typeclass for Functor, Bifunctor, Trifunctor, etc.
+homepage:            https://github.com/gelisam/n-ary-functor
+license:             PublicDomain
+author:              Samuel Gélineau
+maintainer:          gelisam+github@gmail.com
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+extra-source-files:  README.md
+                   , CHANGELOG.md
+
+library
+  exposed-modules:     NAryFunctor
+  build-depends:       base >=4.9 && <4.11
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+
+test-suite doctests
+  default-language:   Haskell2010
+  type:               exitcode-stdio-1.0
+  ghc-options:        -threaded
+  main-is:            doctests.hs
+  build-depends:      base >4 && <5, doctest, doctest-discover
+  HS-Source-Dirs:     test
diff --git a/src/NAryFunctor.hs b/src/NAryFunctor.hs
new file mode 100644
--- /dev/null
+++ b/src/NAryFunctor.hs
@@ -0,0 +1,278 @@
+{-# LANGUAGE RankNTypes, TypeFamilies, TypeFamilyDependencies, TypeInType #-}
+module NAryFunctor
+  ( NFunctor(..)
+
+  -- * Internals
+  , NMap1(..), NMap
+  ) where
+
+import Data.Bifunctor
+import Data.Functor.Identity
+import Data.Kind (Type)
+
+
+-- |
+-- A generalization of 'Functor', 'Bifunctor', 'Trifunctor', etc.
+--
+-- Example usage:
+--
+-- >>> nmap <#> (+1) $ Identity (0::Int)
+-- Identity 1
+--
+-- >>> nmap <#> (+1) <#> (+2) $ (0::Int, 0::Int)
+-- (1,2)
+--
+-- >>> nmap <#> (+1) <#> (+2) <#> (+3) $ (0::Int, 0::Int, 0::Int)
+-- (1,2,3)
+--
+-- Laws:
+--
+-- > nmap <#> id <#> ... <#> id = id
+-- > (nmap <#> f1 <#> ... <#> fN) . (nmap <#> g1 <#> ... <#> gN) = nmap <#> (f1 . g1) <#> ... <#> (fN . gN)
+--
+-- Example instance:
+--
+-- > instance NFunctor (,,) where
+-- >   nmap = NMap1 $ \f1
+-- >       -> NMap1 $ \f2
+-- >       -> NMap1 $ \f3
+-- >       -> \(x1,x2,x3)
+-- >       -> (f1 x1, f2 x2, f3 x3)
+class NFunctor (f :: k) where
+  nmap :: NMap k f f
+
+
+-- |
+-- Types like 'Either' which have both a 'Functor' and a 'Bifunctor' instance
+-- can have more than one 'NFunctor' instance. Those instances all define the
+-- same method, 'nmap', but they return a value of a different type, which is
+-- how the correct 'NFunctor' instance is picked:
+--
+-- > nmap :: NMap1 Type (Either a) (Either a)    -- Functor
+-- > nmap :: NMap1 (Type -> Type) Either Either  -- Bifunctor
+--
+-- This 'NMap1' is unwrapped by using '<#>' to pass in the next input function.
+-- In the case of @NMap1 (Type -> Type)@, the result after passing this input
+-- function is another 'NMap1', which needs to be unwrapped using a second
+-- '<#>'. The end result is that the 'Functor' behaviour is obtained by using a
+-- single '<#>', and the 'Bifunctor' behaviour is obtained by using two.
+--
+-- >>> nmap <#> (+1) $ Right (0::Int)
+-- Right 1
+-- >>> nmap <#> (+1) <#> (+2) $ Left (0::Int)
+-- Left 1
+newtype NMap1 k (f :: Type -> k) (f' :: Type -> k) = NMap1
+  { (<#>) :: forall a b. (a -> b) -> NMap k (f a) (f' b)
+  }
+
+type family NMap k = (r :: k -> k -> Type) | r -> k where
+  NMap Type        = (->)
+  NMap (Type -> k) = NMap1 k
+
+
+-- | For kind @* -> *@ ('Functor'), 'nmap' must be @NMap1 fmap@.
+--
+-- >>> nmap <#> (+1) $ Right (0::Int)
+-- Right 1
+instance NFunctor (Either a) where
+  nmap = NMap1 fmap
+
+-- | For kind @* -> * -> *@ ('Bifunctor'), 'nmap' must be @NMap1 $ \f1 -> NMap1 $ \f2 -> bimap f1 f2@.
+--
+-- >>> nmap <#> (+1) <#> (+2) $ Left (0::Int)
+-- Left 1
+instance NFunctor Either where
+  nmap = NMap1 $ \f1 -> NMap1 $ \f2 -> bimap f1 f2
+
+
+-- |
+-- For kind @*@, 'nmap' must be the identity function. If 'Bifunctor' and
+-- 'Functor' correspond to binary and unary functors, this corresponds to a
+-- "nullary" functor.
+--
+-- >>> nmap ()
+-- ()
+instance NFunctor () where
+  nmap = id
+
+instance NFunctor Identity where
+  nmap = NMap1 $ \f1
+      -> \(Identity x1)
+      -> Identity (f1 x1)
+
+instance NFunctor (,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> \(x1,x2)
+      -> (f1 x1, f2 x2)
+
+instance NFunctor (,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> \(x1,x2,x3)
+      -> (f1 x1, f2 x2, f3 x3)
+
+instance NFunctor (,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> \(x1,x2,x3,x4)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4)
+
+instance NFunctor (,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> \(x1,x2,x3,x4,x5)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5)
+
+instance NFunctor (,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> \(x1,x2,x3,x4,x5,x6)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6)
+
+instance NFunctor (,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> \(x1,x2,x3,x4,x5,x6,x7)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7)
+
+instance NFunctor (,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8)
+
+instance NFunctor (,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9)
+
+instance NFunctor (,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10)
+
+instance NFunctor (,,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> NMap1 $ \f11
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11)
+
+instance NFunctor (,,,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> NMap1 $ \f11
+      -> NMap1 $ \f12
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12)
+
+instance NFunctor (,,,,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> NMap1 $ \f11
+      -> NMap1 $ \f12
+      -> NMap1 $ \f13
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13)
+
+instance NFunctor (,,,,,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> NMap1 $ \f11
+      -> NMap1 $ \f12
+      -> NMap1 $ \f13
+      -> NMap1 $ \f14
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13, f14 x14)
+
+instance NFunctor (,,,,,,,,,,,,,,) where
+  nmap = NMap1 $ \f1
+      -> NMap1 $ \f2
+      -> NMap1 $ \f3
+      -> NMap1 $ \f4
+      -> NMap1 $ \f5
+      -> NMap1 $ \f6
+      -> NMap1 $ \f7
+      -> NMap1 $ \f8
+      -> NMap1 $ \f9
+      -> NMap1 $ \f10
+      -> NMap1 $ \f11
+      -> NMap1 $ \f12
+      -> NMap1 $ \f13
+      -> NMap1 $ \f14
+      -> NMap1 $ \f15
+      -> \(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15)
+      -> (f1 x1, f2 x2, f3 x3, f4 x4, f5 x5, f6 x6, f7 x7, f8 x8, f9 x9, f10 x10, f11 x11, f12 x12, f13 x13, f14 x14, f15 x15)
+
+-- 16-tuples don't even have a Show instance, so we don't bother with an NFunctor instance either
diff --git a/test/doctests.hs b/test/doctests.hs
new file mode 100644
--- /dev/null
+++ b/test/doctests.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF doctest-discover #-}
