diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011, Jesse A. Tov
+Copyright (c) 2011–2021, Jesse A. Tov
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
diff --git a/memoize.cabal b/memoize.cabal
--- a/memoize.cabal
+++ b/memoize.cabal
@@ -1,15 +1,15 @@
+cabal-version:  2.2
 name:           memoize
-version:        0.8.1
-cabal-version:  >= 1.8
-license:        BSD3
+version:        1.0.0
+license:        BSD-3-Clause
 license-file:   LICENSE
 stability:      experimental
-author:         Jesse A. Tov <jesse@eecs.northwestern.edu>
-maintainer:     jesse@eecs.northwestern.edu
+author:         Jesse A. Tov <jesse.tov@gmail.com>
+maintainer:     jesse.tov@gmail.com
 category:       Data
 synopsis:       A memoization library
 build-type:     Simple
-tested-with:    GHC == 7.10.3, GHC == 7.8.4, GHC == 7.6.3
+tested-with:    GHC == 9.0.1, GHC == 8.10.7, GHC == 8.6.5
 
 description:
         This library provides a type class 'Memoizable' for memoizing
@@ -25,6 +25,7 @@
 library
   build-depends:        base >=3 && <5,
                         template-haskell >=2 && <3
+  default-language:     Haskell98
 
   ghc-options:          -Wall -fno-warn-orphans
   hs-source-dirs:       src
@@ -35,17 +36,27 @@
     Data.Function.Memoize.Class
 
 test-suite memoize-test1
+    Default-Language: Haskell98
     Hs-Source-Dirs: test
     Type: exitcode-stdio-1.0
     Main-is: test1.hs
     build-depends: base, memoize
 
 test-suite memoize-test2
+    Default-Language: Haskell98
     Hs-Source-Dirs: test
     Type: exitcode-stdio-1.0
     Main-is: test2.hs
     build-depends: base, memoize
-    
+
+test-suite memoize-test3
+    Default-Language: Haskell98
+    Hs-Source-Dirs: test
+    Other-Modules: Test3Helper
+    Type: exitcode-stdio-1.0
+    Main-is: test3.hs
+    build-depends: base, memoize
+
 source-repository head
   type:                 git
   location:             git://github.com/tov/memoize.git
diff --git a/src/Data/Function/Memoize/TH.hs b/src/Data/Function/Memoize/TH.hs
--- a/src/Data/Function/Memoize/TH.hs
+++ b/src/Data/Function/Memoize/TH.hs
@@ -15,11 +15,51 @@
 #if __GLASGOW_HASKELL__ < 710
 import Control.Applicative
 #endif
+
 import Control.Monad
 import Language.Haskell.TH
-
 import Data.Function.Memoize.Class
 
+---
+--- `#DEFINE`S FOR VERSION COMPATIBILITY
+---
+
+-- GHC 7.6 changed to StarT from StarK:
+#if __GLASGOW_HASKELL__ >= 706
+#  define COMPAT_STAR StarT
+#else
+#  define COMPAT_STAR StarK
+#endif
+
+--- TH 2.10 treats type classes like type constructors:
+#if MIN_VERSION_template_haskell(2,10,0)
+#  define COMPAT_CLASS_PRED(C)  (appT (conT (C)) . varT)
+#else
+#  define COMPAT_CLASS_PRED(C)  (classP (C) . (:[]) . varT)
+#endif
+
+-- TH 2.11 supports GADTs and adds a field to NewtypeD and DataD:
+#if MIN_VERSION_template_haskell(2,11,0)
+#  define COMPAT_TH_GADTS
+#  define COMPAT_NEWTYPE_D(N, T, C)  (NewtypeD _ (N) (T) _ (C) _)
+#  define COMPAT_DATA_D(N, T, C)     (DataD _ (N) (T) _ (C) _)
+#else
+#  undef  COMPAT_TH_GADTS
+#  define COMPAT_NEWTYPE_D(N, T, C)  (NewtypeD _ (N) (T) (C) _)
+#  define COMPAT_DATA_D(N, T, C)     (DataD _ (N) (T) (C) _)
+#endif
+
+-- GHC 9 adds a type parameter to the TyVarBndr type:
+#if __GLASGOW_HASKELL__ >= 900
+#  define COMPAT_TY_VAR_BNDR(V)      (TyVarBndr (V))
+#  define COMPAT_PLAIN_TV(N)         (PlainTV (N) _)
+#  define COMPAT_KINDED_TV(N, K)     (KindedTV (N) _ (K))
+#else
+#  define COMPAT_TY_VAR_BNDR(V)      TyVarBndr
+#  define COMPAT_PLAIN_TV(N)         (PlainTV (N))
+#  define COMPAT_KINDED_TV(N, K)     (KindedTV (N) (K))
+#endif
+
 -- |
 -- To derive 'Memoizable' instances for the given data types.
 -- In the simplest usage, to derive 'Memoizable' for an algebraic
@@ -91,7 +131,7 @@
 -- context for the 'Memoizable' instance.  Instead, one can write:
 --
 -- @
---   instance ('Enum' a, 'Bounded' a, 'Memoizable' b) =>
+--   instance ('Eq' a, 'Enum' a, 'Bounded' a, 'Memoizable' b) =>
 --            'Memoizable' (T a b) where
 --     memoize = $(deriveMemoize ''T)
 -- @
@@ -116,30 +156,31 @@
 --   corresponds to a @data@ or @newtype@, and if so, returns the name,
 --   a list of its parameters, and a list of constructor names with
 --   their arities.
-checkName ∷ Name → Q (Name, [TyVarBndr], [(Name, Int)])
+checkName ∷ Name → Q (Name, [COMPAT_TY_VAR_BNDR(())], [(Name, Int)])
 checkName name0 = do
-  info            ← reify name0
-  case info of
-#if MIN_VERSION_template_haskell(2,11,0)
-    TyConI (DataD _ name tvbs _ cons _)
-#else
-    TyConI (DataD _ name tvbs cons _)
-#endif
-               → return (name, tvbs, stdizeCon <$> cons)
-#if MIN_VERSION_template_haskell(2,11,0)
-    TyConI (NewtypeD _ name tvbs _ con _)
-#else
-    TyConI (NewtypeD _ name tvbs con _)
+  let can'tDerive      = "deriveMemoizable: Can’t derive a Memoizable " ++
+                         "instance for ‘" ++ show name0 ++ "’ because "
+      can'tDeriveNonTC = can'tDerive ++ "it isn’t a type constructor."
+      can'tDeriveGadt  = can'tDerive ++ "GADTs aren’t supported."
+      --
+      stdizeCon (NormalC name params) = return (name, length params)
+      stdizeCon (RecC name fields)    = return (name, length fields)
+      stdizeCon (InfixC _ name _)     = return (name, 2)
+      stdizeCon (ForallC _ _ con)     = stdizeCon con
+#ifdef COMPAT_TH_GADTS
+      stdizeCon (GadtC _ _ _)         = fail can'tDeriveGadt
+      stdizeCon (RecGadtC _ _ _)      = fail can'tDeriveGadt
 #endif
-               → return (name, tvbs, [stdizeCon con])
-    _          → fail $
-      "deriveMemoizable: Can't derive a Memoizable instance for `" ++
-      show name0 ++ "' because it isn't a type constructor."
-  where
-    stdizeCon (NormalC name params) = (name, length params)
-    stdizeCon (RecC name fields)    = (name, length fields)
-    stdizeCon (InfixC _ name _)     = (name, 2)
-    stdizeCon (ForallC _ _ con)     = stdizeCon con
+  --
+  info ← reify name0
+  case info of
+    TyConI (COMPAT_DATA_D(name, tvbs, cons)) → do
+      conInfos ← mapM stdizeCon cons
+      return (name, tvbs, conInfos)
+    TyConI (COMPAT_NEWTYPE_D(name, tvbs, con)) → do
+      conInfo ← stdizeCon con
+      return (name, tvbs, [conInfo])
+    _ → fail can'tDeriveNonTC
 
 -- | Given a list, produces a list of nicely printable, distinct names.
 --   Used so that instances print with nice parameters names, like
@@ -167,25 +208,17 @@
 -- choose the parameters that have no explicit kind from the
 -- list of binders. The third argument gives the actual type variable
 -- names to use.
-buildContext ∷ Maybe [Int] → [TyVarBndr] → [Name] → CxtQ
+buildContext ∷ Maybe [Int] → [COMPAT_TY_VAR_BNDR(a)] → [Name] → CxtQ
 buildContext mindices tvbs tvs =
-#if MIN_VERSION_template_haskell(2,10,0)
-  cxt (appT (conT ''Memoizable) . varT <$> cxttvs)
-#else
-  cxt (classP ''Memoizable . (:[]) . varT <$> cxttvs)
-#endif
+  cxt (COMPAT_CLASS_PRED(''Memoizable) <$> cxttvs)
   where
   cxttvs = case mindices of
     Just ixs → filterBy (`elem` ixs) [1 ..] tvs
     Nothing  → filterBy isStar       tvbs   tvs
   --
-  isStar (PlainTV _) = True
-#if __GLASGOW_HASKELL__ >= 706
-  isStar (KindedTV _ StarT) = True
-#else
-  isStar (KindedTV _ StarK) = True
-#endif
-  isStar (KindedTV _ _) = False
+  isStar (COMPAT_PLAIN_TV(_))               = True
+  isStar (COMPAT_KINDED_TV(_, COMPAT_STAR)) = True
+  isStar _                                  = False
   --
   filterBy ∷ (a → Bool) → [a] → [b] → [b]
   filterBy p xs ys = snd <$> filter (p . fst) (zip xs ys)
@@ -224,30 +257,31 @@
 -- above 'buildMethodDec'
 buildMethodExp ∷ [(Name, Int)] → ExpQ
 buildMethodExp cons = do
-  f      ← newName "f"
-  look   ← newName "look"
-  caches ← mapM (\ _ -> newName "cache") cons
+  f      ← newName "fun"
+  caches ← mapM (\_ → newName "cache") cons
   lam1E (varP f)
     (letE
-      (buildLookup look cons caches
-        : zipWith (buildCache f) cons caches)
-      (varE look))
+      (zipWith (buildCache f) cons caches)
+      (buildLookup cons caches))
 
 -- | Build the look function by building a clause for each constructor
 --   of the datatype.
-buildLookup ∷ Name → [(Name, Int)] → [Name] → DecQ
-buildLookup look cons caches =
-  funD look (zipWith buildLookupClause cons caches)
+buildLookup ∷ [(Name, Int)] → [Name] → ExpQ
+buildLookup cons caches = do
+  a ← newName "arg"
+  lam1E (varP a) .
+    caseE (varE a) $
+      zipWith buildLookupMatch cons caches
 
 -- | Build a lookup clause for one constructor.  We lookup a value
 --   by matching that constructor and then passing its parameters to
 --   the cache for that constructor.
-buildLookupClause ∷ (Name, Int) → Name → ClauseQ
-buildLookupClause (con, arity) cache = do
-  params ← replicateM arity (newName "a")
-  clause [conP con (varP <$> params)]
-         (normalB (foldl appE (varE cache) (varE <$> params)))
-         []
+buildLookupMatch ∷ (Name, Int) → Name → MatchQ
+buildLookupMatch (con, arity) cache = do
+  params ← replicateM arity (newName "param")
+  match (conP con (varP <$> params))
+        (normalB (foldl appE (varE cache) (varE <$> params)))
+        []
 
 -- | Build the definition of a cache for the given constructor.  We do
 --   this by binding the cache name to a cascading sequence of
@@ -262,5 +296,5 @@
 composeMemos ∷ Int → Name → ExpQ → ExpQ
 composeMemos 0     f arg = [| $(varE f) $arg |]
 composeMemos arity f arg = do
-  [| memoize $ \b -> $(composeMemos (arity - 1) f [| $arg b |]) |]
+  [| memoize $ \b → $(composeMemos (arity - 1) f [| $arg b |]) |]
 
diff --git a/test/Test3Helper.hs b/test/Test3Helper.hs
new file mode 100644
--- /dev/null
+++ b/test/Test3Helper.hs
@@ -0,0 +1,3 @@
+module Test3Helper where
+
+data NonstandardParams a b = NonstandardParams (a -> Bool) b
diff --git a/test/test3.hs b/test/test3.hs
new file mode 100644
--- /dev/null
+++ b/test/test3.hs
@@ -0,0 +1,36 @@
+{-# language TemplateHaskell, GADTs #-}
+
+import Data.Function.Memoize
+import Control.Monad (forM_, when)
+import Test3Helper
+
+-- NonstandardParams is defined by:
+--
+--   data NonstandardParams a b
+--     = NonstandardParams (a -> Bool) b
+--
+-- This won’t compile because it needs addition typeclass constraints in
+-- the instance context:
+--
+--   $(deriveMemoizable ''NonstandardParams)
+
+instance (Eq a, Enum a, Bounded a, Memoizable b) => Memoizable (NonstandardParams a b) where
+  memoize = $(deriveMemoize ''NonstandardParams)
+
+applyToLength :: NonstandardParams Bool Int -> Bool
+applyToLength (NonstandardParams f z) = f (odd z)
+
+cases = [ (NonstandardParams id 5, True)
+        , (NonstandardParams id 6, False)
+        , (NonstandardParams not 5, False)
+        , (NonstandardParams not 6, True)
+        ]
+
+main :: IO ()
+main = do
+  let memoized = memoize applyToLength
+  forM_ cases $ \(input, expected) -> do
+    let actual = applyToLength input
+    when (actual /= expected) $
+      fail $ "Test failed: got " ++ show actual ++
+             " when " ++ show expected ++ " expected."
