diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -97,3 +97,47 @@
 **v0.3.0.1**
 
   * Support GHC 9.0, base 4.15, template-haskell 2.17
+
+**v1**
+
+Removed the `Data` prefix from module names.
+
+  * `Data.D10.Char` is now `D10.Char`
+  * `Data.D10.Num` is now `D10.Num`
+  * `Data.D10.Predicate` is now `D10.Predicate`
+  * `Data.D10.Safe` is now `D10.Safe`
+
+Constructors for `D10` are no longer exported by the modules formerly
+known as `Data.D10.Char` and `Data.D10.Num`. They have moved to the
+new modules `D10.Char.Unsafe` and `D10.Num.Unsafe` respectively.
+
+Operators `(+)`, `(-)`, and `(*)` have been moved into their own
+separate modules, as the names conflict with `Prelude` functions.
+These new modules are:
+
+  * `D10.Char.Arithmetic`
+  * `D10.Num.Arithmetic`
+  * `D10.Safe.Arithmetic`
+
+Other new modules:
+
+  * `D10.Char.Conversions`
+  * `D10.Char.Quotes`
+  * `D10.Char.Splices`
+  * `D10.Char.Type`
+  * `D10.Num.Conversions`
+  * `D10.Num.Quotes`
+  * `D10.Num.Splices`
+  * `D10.Num.Type`
+  * `D10.Safe.Conversions`
+  * `D10.Safe.Quotes`
+  * `D10.Safe.Splices`
+  * `D10.Safe.Type`
+
+All instances of the `Language.Haskell.TH.Syntax.Lift` class
+have been removed
+
+Added *Safe Haskell* language flags (`Safe`, `Trustworthy`, `Unsafe`)
+to indicate which modules permit constructing invalid values
+
+Required Cabal version required is raised from 2.2 to 3.0
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,36 +4,12 @@
 
 ## Modules
 
-Each of the following modules defines a different type named
-`D10`, all of which are different representations of the same
-concept:
-
-* `Data.D10.Char` - Defines a `D10` type as a newtype for `Char`,
-  where the values are restricted to characters between `'0'` and
-  `'9'`.
-* `Data.D10.Num` - Defines a `D10` type as a newtype for any
-  type with an instance of the `Num` class, where the values
-  are restricted to numbers between `fromInteger 0` and
-  `fromInteger 9`.
-* `Data.D10.Safe` - Defines a `D10` type as
-  `D0 | D1 | D2 | ... | D9`.
-
-Other modules:
+Each of the following modules defines a different type named `D10`, all of which are different representations of the same concept:
 
-* `Data.D10.Predicate` - Functions to test whether values of
-  various types represent digits in the range *0* to *9*.
+* `D10.Char` - Defines a `D10` type as a newtype for `Char`, where the values are restricted to characters between `'0'` and `'9'`.
+* `D10.Num` - Defines a `D10` type as a newtype for any type with an instance of the `Num` class, where the values are restricted to numbers between `fromInteger 0` and `fromInteger 9`.
+* `D10.Safe` - Defines a `D10` type as `D0 | D1 | D2 | ... | D9`.
 
 ## Quasi-quoters
 
-Each module that defines a `D10` type also defines quasi-quoters
-for it. With the `QuasiQuotes` GHC extension enabled, a single
-digit like *7* can be written as `[d10|7|]`, and a list of digits
-like *[4,5,6]* can be written as `[d10|456|]`. For `Data.D10.Char`
-and `Data.D10.Num`, the quasi-quoters are an important feature,
-because the `D10` types defined in these modules have unsafe
-constructors, and the quasi-quoters provide compile-time assurance
-that we never construct a `D10` that represents a value outside
-the range *0* to *9*. For `Data.D10.Safe`, the quasi-quoter is
-offered merely as a possible convenience, allowing you to write
-`[d10|456789|]` in place of the somewhat longer expression
-`[D4,D5,D6,D7,D8,D9]`.
+Each module that defines a `D10` type also defines quasi-quoters for it. With the `QuasiQuotes` GHC extension enabled, a single digit like *7* can be written as `[d10|7|]`, and a list of digits like *[4,5,6]* can be written as `[d10|456|]`. For `D10.Char` and `D10.Num`, the quasi-quoters are an important feature, because the `D10` types defined in these modules have unsafe constructors, and the quasi-quoters provide compile-time assurance that we never construct a `D10` that represents a value outside the range *0* to *9*.
diff --git a/d10.cabal b/d10.cabal
--- a/d10.cabal
+++ b/d10.cabal
@@ -1,49 +1,59 @@
-cabal-version: 2.2
+cabal-version: 3.0
 
 name: d10
-version: 0.3.0.1
+version: 1
 category: Data
 synopsis: Digits 0-9
 
 description:
     Data types representing the digits zero through nine.
-    .
+
     == Modules
-    .
+
     Each of the following modules defines a different type named
     @D10@, all of which are different representations of the same
     concept:
-    .
-    * "Data.D10.Char" - Defines a @D10@ type as a newtype for @Char@,
-      where the values are restricted to characters between @'0'@ and
-      @'9'@.
-    * "Data.D10.Num" - Defines a @D10@ type as a newtype for any
+
+    * "D10.Char" - Defines a @D10@ type as a newtype for @Char@,
+      where the values are restricted to characters between
+      @'0'@ and @'9'@
+    * "D10.Num" - Defines a @D10@ type as a newtype for any
       type with an instance of the @Num@ class, where the values
       are restricted to numbers between @fromInteger 0@ and
-      @fromInteger 9@.
-    * "Data.D10.Safe" - Defines a @D10@ type as
-      @D0 | D1 | D2 | ... | D9@.
-    .
-    Other modules:
-    .
-    * "Data.D10.Predicate" - Functions to test whether values of
-      various types represent digits in the range /0/ to /9/.
-    .
+      @fromInteger 9@
+    * "D10.Safe" - Defines a @D10@ type as @D0 | D1 | D2 | ... | D9@
+
+    The @Arithmetic@ modules provide infix operators (@+@), (@-@), (@*@)
+    for modular arithmetic:
+
+    * "D10.Char.Arithmetic"
+    * "D10.Num.Arithmetic"
+    * "D10.Safe.Arithmetic"
+
+    The following modules export constructors that can be used
+    incorrectly:
+
+    * "D10.Char.Unsafe"
+    * "D10.Num.Unsafe"
+
+    Functions to test whether values of various types represent
+    digits in the range 0 to 9:
+
+    * "D10.Predicate"
+
     == Quasi-quoters
-    .
-    Each module that defines a @D10@ type also defines quasi-quoters
-    for it. With the @QuasiQuotes@ GHC extension enabled, a single
-    digit like /7/ can be written as @[d10|7|]@, and a list of digits
-    like /[4,5,6]/ can be written as @[d10|456|]@. For "Data.D10.Char"
-    and "Data.D10.Num", the quasi-quoters are an important feature,
-    because the @D10@ types defined in these modules have unsafe
-    constructors, and the quasi-quoters provide compile-time assurance
-    that we never construct a @D10@ that represents a value outside
-    the range /0/ to /9/. For "Data.D10.Safe", the quasi-quoter is
-    offered merely as a possible convenience, allowing you to write
-    @[d10|456789|]@ in place of the somewhat longer expression
-    @[D4,D5,D6,D7,D8,D9]@.
 
+    * The "D10.Char" and "D10.Num" modules include quasi-quoters
+      named @d10@ used to express single digits. For example, 7
+      can be written as @[d10|7|]@. This is an important feature
+      because the @D10@ types defined in these modules have
+      unsafe constructors, and the quasi-quoters provide
+      compile-time assurance that we never construct a @D10@
+      that represents a value outside the range 0 to 9.
+    * "D10.Char", "D10.Num", and "D10.Safe" include quasi-quoters
+      named @d10list@ to express lists of digits. For example,
+      [4, 5, 6] can be written as @[d10list|456|]@.
+
 homepage:    https://github.com/typeclasses/d10
 bug-reports: https://github.com/typeclasses/d10/issues
 
@@ -57,143 +67,80 @@
 build-type: Simple
 
 tested-with:
-    GHC == 8.2.2
-  , GHC == 8.4.3
-  , GHC == 8.6.1
-  , GHC == 8.8.1
-  , GHC == 8.10.1
-  , GHC == 9.0.1
+    GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1
+  , GHC == 8.8.1, GHC == 8.10.1, GHC == 9.0.1
 
 extra-source-files:
     README.md
     CHANGELOG.md
 
 source-repository head
-  type: git
-  location: https://github.com/typeclasses/d10
+    type: git
+    location: https://github.com/typeclasses/d10
 
-library
-  default-language: Haskell2010
-  ghc-options: -Wall
-  hs-source-dirs: src
+common language
+    default-language: Haskell2010
+    ghc-options: -Wall
+    default-extensions:
+        DeriveDataTypeable DeriveGeneric DeriveLift
+        DerivingStrategies InstanceSigs QuasiQuotes
+        ScopedTypeVariables TypeApplications
 
-  build-depends:
-      base ^>= 4.10 || ^>= 4.11 || ^>= 4.12
-        || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-    , template-haskell
-           ^>= 2.12 || ^>= 2.13 || ^>= 2.14
-        || ^>= 2.15 || ^>= 2.16 || ^>= 2.17
+common library-dependencies
+    build-depends:
+        base ^>= 4.10 || ^>= 4.11 || ^>= 4.12
+          || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
+      , template-haskell
+             ^>= 2.12 || ^>= 2.13 || ^>= 2.14
+          || ^>= 2.15 || ^>= 2.16 || ^>= 2.17
 
-  exposed-modules:
-      Data.D10.Char
-    , Data.D10.Num
-    , Data.D10.Predicate
-    , Data.D10.Safe
+common test
+    import: language
+    import: library-dependencies
+    hs-source-dirs: test
+    other-modules: AssertQFails Fallible
+    build-depends: d10, hedgehog ^>= 1.0
+    default-extensions: GeneralizedNewtypeDeriving
+                        TemplateHaskell
 
-  default-extensions:
-      DeriveDataTypeable
-      DeriveGeneric
-      DeriveLift
-      DerivingStrategies
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      QuasiQuotes
-      ScopedTypeVariables
-      TemplateHaskell
-      TypeApplications
+library
+    import: language
+    import: library-dependencies
+    hs-source-dirs: src
+    exposed-modules:
+        D10.Char
+          , D10.Char.Arithmetic
+          , D10.Char.Conversions
+          , D10.Char.Quotes
+          , D10.Char.Splices
+          , D10.Char.Type
+          , D10.Char.Unsafe
+      , D10.Num
+          , D10.Num.Arithmetic
+          , D10.Num.Conversions
+          , D10.Num.Quotes
+          , D10.Num.Splices
+          , D10.Num.Type
+          , D10.Num.Unsafe
+      , D10.Safe
+          , D10.Safe.Arithmetic
+          , D10.Safe.Conversions
+          , D10.Safe.Quotes
+          , D10.Safe.Splices
+          , D10.Safe.Type
+      , D10.Predicate
 
 test-suite char
-  default-language: Haskell2010
-  ghc-options: -Wall
-  hs-source-dirs: test
-  type: exitcode-stdio-1.0
-  main-is: char.hs
-
-  other-modules:
-      AssertQFails
-      Fallible
-
-  build-depends:
-      d10
-    , base ^>= 4.10 || ^>= 4.11 || ^>= 4.12
-        || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-    , hedgehog ^>= 1.0
-    , template-haskell
-           ^>= 2.12 || ^>= 2.13 || ^>= 2.14
-        || ^>= 2.15 || ^>= 2.16 || ^>= 2.17
-
-  default-extensions:
-      DeriveDataTypeable
-      DeriveGeneric
-      DeriveLift
-      DerivingStrategies
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      QuasiQuotes
-      ScopedTypeVariables
-      TemplateHaskell
-      TypeApplications
+    import: test
+    type: exitcode-stdio-1.0
+    main-is: char.hs
 
 test-suite num
-  default-language: Haskell2010
-  ghc-options: -Wall
-  hs-source-dirs: test
-  type: exitcode-stdio-1.0
-  main-is: num.hs
-
-  other-modules:
-      AssertQFails
-      Fallible
-
-  build-depends:
-      d10
-    , base ^>= 4.10 || ^>= 4.11 || ^>= 4.12
-        || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-    , hedgehog ^>= 1.0
-    , template-haskell
-           ^>= 2.12 || ^>= 2.13 || ^>= 2.14
-        || ^>= 2.15 || ^>= 2.16 || ^>= 2.17
-
-  default-extensions:
-      DeriveDataTypeable
-      DeriveGeneric
-      DeriveLift
-      DerivingStrategies
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      QuasiQuotes
-      ScopedTypeVariables
-      TemplateHaskell
-      TypeApplications
+    import: test
+    type: exitcode-stdio-1.0
+    main-is: num.hs
 
 test-suite safe
-  default-language: Haskell2010
-  ghc-options: -Wall
-  hs-source-dirs: test
-  type: exitcode-stdio-1.0
-  main-is: safe.hs
-
-  other-modules:
-      AssertQFails
-      Fallible
-
-  build-depends:
-      d10
-    , base ^>= 4.10 || ^>= 4.11 || ^>= 4.12
-        || ^>= 4.13 || ^>= 4.14 || ^>= 4.15
-    , hedgehog ^>= 1.0
-    , template-haskell
-           ^>= 2.12 || ^>= 2.13 || ^>= 2.14
-        || ^>= 2.15 || ^>= 2.16 || ^>= 2.17
-
-  default-extensions:
-      DeriveDataTypeable
-      DeriveGeneric
-      DeriveLift
-      DerivingStrategies
-      GeneralizedNewtypeDeriving
-      InstanceSigs
-      QuasiQuotes
-      ScopedTypeVariables
-      TemplateHaskell
-      TypeApplications
+    import: test
+    type: exitcode-stdio-1.0
+    main-is: safe.hs
diff --git a/src/D10/Char.hs b/src/D10/Char.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char.hs
@@ -0,0 +1,132 @@
+{-# language Safe #-}
+
+{- |
+
+Defines a 'D10' type as a newtype for 'Char', where the
+values are restricted to characters between @'0'@ and @'9'@.
+
+This module provides many functions for constructing 'D10'
+values, including:
+
+  * @'integerD10Maybe' :: 'Integer' -> 'Maybe' 'D10'@
+  * @'integerMod10' :: 'Integer' -> 'D10'@
+
+With the @QuasiQuotes@ GHC extension enabled, you can write
+'D10' literals using the quasi-quoters 'd10' and 'd10list'.
+
+-}
+
+module D10.Char
+    (
+    -- * Related modules
+    -- $modules
+
+    -- * Type
+      D10
+
+    -- ** Bounded
+    -- $bounded
+
+    -- ** Enum
+    -- $enum
+
+    -- ** Show
+    -- $show
+
+    -- * Quasi-quoters
+    , d10, d10list
+
+    -- * Splices
+    -- ** Expressions
+    , d10Exp, d10ListExp
+    -- ** Patterns
+    , d10Pat, d10ListPat
+
+    -- * Conversions
+    -- ** D10 / Char
+    , d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- ** D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- ** [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- ** D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail
+    , natMod10
+    -- ** D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- ** D10 / Int
+    , d10Int, intD10Maybe, intD10Either
+    , intD10Fail, intMod10
+    -- ** D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+
+    ) where
+
+import D10.Char.Conversions
+import D10.Char.Type
+import D10.Char.Splices
+import D10.Char.Quotes
+
+{- $modules
+
+Additional functions related to this 'D10' type may be found in:
+
+  * "D10.Char.Arithmetic"
+
+The contents of the following modules are re-exported here:
+
+  * "D10.Char.Conversions"
+  * "D10.Char.Splices"
+  * "D10.Char.Quotes"
+  * "D10.Char.Type"
+
+The unsafe constructor for 'D10' can be found in:
+
+  * "D10.Char.Unsafe"
+
+The following modules define @D10@ types in different ways
+but are otherwise very similar to this one:
+
+  * "D10.Num"
+  * "D10.Safe"
+
+-}
+
+{- $bounded
+
+>>> minBound :: D10
+[d10|0|]
+
+>>> maxBound :: D10
+[d10|9|]
+
+-}
+
+{- $enum
+
+>>> [ [d10|5|] .. ]
+[d10list|56789|]
+
+>>> [ [d10|4|] .. [d10|7|] ]
+[d10list|4567|]
+
+>>> [ [d10|5|], [d10|4|] .. ]
+[d10list|543210|]
+
+>>> [ [d10|1|], [d10|3|] .. ]
+[d10list|13579|]
+
+>>> [ minBound .. maxBound ] :: [D10]
+[d10list|0123456789|]
+
+-}
+
+{- $show
+
+'show' shows base-10 digits using the quasiquoters defined
+in this module. A single digit is displayed using 'd10'.
+A list of digits is displayed using 'd10list'.
+
+-}
diff --git a/src/D10/Char/Arithmetic.hs b/src/D10/Char/Arithmetic.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Arithmetic.hs
@@ -0,0 +1,40 @@
+{-# language Safe #-}
+
+module D10.Char.Arithmetic where
+
+import qualified Prelude as P
+
+import D10.Char (D10, d10Int, intMod10)
+
+-- | Addition modulo 10.
+--
+-- >>> [d10|2|] + [d10|3|]
+-- [d10|5|]
+--
+-- >>> [d10|6|] + [d10|7|]
+-- [d10|3|]
+
+(+) :: D10 -> D10 -> D10
+x + y = intMod10 (d10Int x P.+ d10Int y)
+
+-- | Subtraction modulo 10.
+--
+-- >>> [d10|7|] - [d10|5|]
+-- [d10|2|]
+--
+-- >>> [d10|3|] - [d10|7|]
+-- [d10|6|]
+
+(-) :: D10 -> D10 -> D10
+x - y = intMod10 (d10Int x P.- d10Int y)
+
+-- | Multiplication modulo 10.
+--
+-- >>> [d10|2|] * [d10|4|]
+-- [d10|8|]
+--
+-- >>> [d10|7|] * [d10|8|]
+-- [d10|6|]
+
+(*) :: D10 -> D10 -> D10
+x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/src/D10/Char/Conversions.hs b/src/D10/Char/Conversions.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Conversions.hs
@@ -0,0 +1,547 @@
+{-# language Trustworthy #-}
+
+module D10.Char.Conversions
+    (
+    -- * D10 / Char
+      d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- * D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- * [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- * D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail
+    , natMod10
+    -- * D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- * D10 / Int
+    , d10Int, intD10Maybe, intD10Either
+    , intD10Fail, intMod10
+    -- * D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+    ) where
+
+import D10.Char.Unsafe (D10(..))
+import D10.Predicate (isD10Char, isD10Int, isD10Integer, isD10Nat)
+
+-- base
+import Control.Monad.Fail (MonadFail (fail))
+import Data.Char          (chr, ord)
+import Numeric.Natural    (Natural)
+import Prelude            hiding (fail)
+
+import qualified Prelude as P
+
+-- | Convert a 'D10' to its underlying 'Char' representation.
+--
+-- >>> d10Char [d10|7|]
+-- '7'
+
+d10Char :: D10 -> Char
+d10Char (D10_Unsafe x) = x
+
+-- | Convert a 'D10' to a 'String'.
+--
+-- @'d10Str' x = ['d10Char' x]@
+--
+-- >>> d10Str [d10|7|]
+-- "7"
+
+d10Str :: D10 -> String
+d10Str (D10_Unsafe x) = [x]
+
+-- | Convert a 'D10' to a 'Natural'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Nat [d10|7|]
+-- 7
+
+d10Nat :: D10 -> Natural
+d10Nat (D10_Unsafe x) = fromIntegral (ord x P.- ord '0')
+
+-- | Convert a 'D10' to an 'Integer'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Integer [d10|7|]
+-- 7
+
+d10Integer :: D10 -> Integer
+d10Integer (D10_Unsafe x) = toInteger (ord x P.- ord '0')
+
+-- | Convert a 'D10' to an 'Int'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Int [d10|7|]
+-- 7
+
+d10Int :: D10 -> Int
+d10Int (D10_Unsafe x) = ord x P.- ord '0'
+
+-- | Convert a 'D10' to any kind of number with a 'Num' instance.
+--
+-- Specialized versions of this function include 'd10Nat',
+-- 'd10Integer', and 'd10Int'.
+--
+-- >>> d10Num [d10|7|] :: Integer
+-- 7
+
+d10Num :: Num a => D10 -> a
+d10Num (D10_Unsafe x) = fromIntegral (ord x P.- ord '0')
+
+---------------------------------------------------
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> natMod10 56
+-- [d10|6|]
+
+natMod10 :: Natural -> D10
+natMod10 x = D10_Unsafe (chr (ord '0' P.+ fromIntegral (x `mod` 10)))
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> integerMod10 56
+-- [d10|6|]
+--
+-- >>> integerMod10 (-56)
+-- [d10|4|]
+
+integerMod10 :: Integer -> D10
+integerMod10 x = D10_Unsafe (chr (ord '0' P.+ fromInteger (x `mod` 10)))
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> intMod10 56
+-- [d10|6|]
+--
+-- >>> intMod10 (-56)
+-- [d10|4|]
+
+intMod10 :: Int -> D10
+intMod10 x = D10_Unsafe (chr (ord '0' P.+ (x `mod` 10)))
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given number
+-- (whose type must have an instance of the 'Integral' class).
+--
+-- Specialized versions of this function include 'natMod10',
+-- 'integerMod10', and 'intMod10'.
+--
+-- >>> integralMod10 (56 :: Integer)
+-- [d10|6|]
+--
+-- >>> integralMod10 ((-56) :: Integer)
+-- [d10|4|]
+
+integralMod10 :: Integral a => a -> D10
+integralMod10 x = D10_Unsafe (chr (ord '0' P.+ fromIntegral (x `mod` 10)))
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
+--
+-- 'charD10Fail' is a more general version of this function.
+--
+-- >>> charD10Maybe '5'
+-- Just [d10|5|]
+--
+-- >>> charD10Maybe 'a'
+-- Nothing
+
+charD10Maybe :: Char -> Maybe D10
+charD10Maybe x
+        | isD10Char x  =  Just (D10_Unsafe x)
+        | otherwise    =  Nothing
+
+-- | Convert a 'String' to a 'D10' if it consists of exactly one
+-- character and that character is within the range @'0'@ to @'9'@,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
+--
+-- 'strD10Fail' is a more general version of this function.
+--
+-- >>> strD10Maybe "5"
+-- Just [d10|5|]
+--
+-- >>> strD10Maybe "a"
+-- Nothing
+--
+-- >>> strD10Maybe "58"
+-- Nothing
+
+strD10Maybe :: String -> Maybe D10
+strD10Maybe [x] = charD10Maybe x
+strD10Maybe _   = Nothing
+
+-- | Convert a 'String' to a list of 'D10' if all of the characters
+-- in the string are within the range @'0'@ to @'9'@, or produce
+-- 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
+--
+-- 'strD10ListFail' is a more general version of this function.
+--
+-- >>> strD10ListMaybe "5"
+-- Just [d10list|5|]
+--
+-- >>> strD10ListMaybe "a"
+-- Nothing
+--
+-- >>> strD10ListMaybe "58"
+-- Just [d10list|58|]
+
+strD10ListMaybe :: String -> Maybe [D10]
+strD10ListMaybe = traverse charD10Maybe
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> natD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> natD10Maybe 12
+-- Nothing
+
+natD10Maybe :: Natural -> Maybe D10
+natD10Maybe x
+        | isD10Nat x  =  Just (D10_Unsafe (chr (fromIntegral x P.+ ord '0')))
+        | otherwise   =  Nothing
+
+-- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> integerD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> integerD10Maybe 12
+-- Nothing
+--
+-- >>> integerD10Maybe (-5)
+-- Nothing
+
+integerD10Maybe :: Integer -> Maybe D10
+integerD10Maybe x
+        | isD10Integer x  =  Just (D10_Unsafe (chr (fromInteger x P.+ ord '0')))
+        | otherwise       =  Nothing
+
+-- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> intD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> intD10Maybe 12
+-- Nothing
+--
+-- >>> intD10Maybe (-5)
+-- Nothing
+
+intD10Maybe :: Int -> Maybe D10
+intD10Maybe x
+        | isD10Int x  =  Just (D10_Unsafe (chr (x P.+ ord '0')))
+        | otherwise   =  Nothing
+
+-- | Construct a 'D10' from any kind of number with an 'Integral'
+-- instance, or produce 'Nothing' if the number falls outside the
+-- range 0 to 9.
+--
+-- @'D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
+--
+-- Specialized versions of this function include 'natD10Maybe',
+-- 'integerD10Maybe', and 'intD10Maybe'.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integralD10Maybe (5 :: Integer)
+-- Just [d10|5|]
+--
+-- >>> integralD10Maybe (12 :: Integer)
+-- Nothing
+--
+-- >>> integralD10Maybe ((-5) :: Integer)
+-- Nothing
+
+integralD10Maybe :: Integral a => a -> Maybe D10
+integralD10Maybe x = integerD10Maybe (toInteger x)
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> charD10Either '5'
+-- Right [d10|5|]
+--
+-- >>> charD10Either 'a'
+-- Left "d10 must be between 0 and 9"
+
+charD10Either :: Char -> Either String D10
+charD10Either x
+        | isD10Char x  =  Right (D10_Unsafe x)
+        | otherwise    =  Left "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> strD10Either "5"
+-- Right [d10|5|]
+--
+-- >>> strD10Either "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10Either "58"
+-- Left "d10 must be a single character"
+
+strD10Either :: String -> Either String D10
+strD10Either [x]         =  charD10Either x
+strD10Either _           =  Left "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> strD10ListEither "5"
+-- Right [d10list|5|]
+--
+-- >>> strD10ListEither "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10ListEither "58"
+-- Right [d10list|58|]
+
+strD10ListEither :: String -> Either String [D10]
+strD10ListEither = traverse charD10Either
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'Left' with an error message otherwise.
+--
+-- >>> natD10Either 5
+-- Right [d10|5|]
+--
+-- >>> natD10Either 12
+-- Left "d10 must be less than 10"
+
+natD10Either :: Natural -> Either String D10
+natD10Either x =
+    case (natD10Maybe x) of
+        Just y  -> Right y
+        Nothing -> Left "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> integerD10Either 5
+-- Right [d10|5|]
+--
+-- >>> integerD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integerD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+integerD10Either :: Integer -> Either String D10
+integerD10Either x =
+    case (integerD10Maybe x) of
+        Just y  -> Right y
+        Nothing -> Left "d10 must be between 0 and 9"
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> intD10Either 5
+-- Right [d10|5|]
+--
+-- >>> intD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> intD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+intD10Either :: Int -> Either String D10
+intD10Either x =
+    case (intD10Maybe x) of
+        Just y  ->  Right y
+        Nothing ->  Left "d10 must be between 0 and 9"
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> integralD10Either (5 :: Integer)
+-- Right [d10|5|]
+--
+-- >>> integralD10Either (12 :: Integer)
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integralD10Either ((-5) :: Integer)
+-- Left "d10 must be between 0 and 9"
+
+integralD10Either :: Integral a => a -> Either String D10
+integralD10Either x = integerD10Either (toInteger x)
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'charD10Maybe' is a specialized version of this function.
+--
+-- >>> charD10Fail '5' :: IO D10
+-- [d10|5|]
+--
+-- >>> charD10Fail 'a' :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+charD10Fail :: MonadFail m => Char -> m D10
+charD10Fail x
+        | isD10Char x  =  return (D10_Unsafe x)
+        | otherwise    =  fail "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'strD10Maybe' is a specialized version of this function.
+--
+-- >>> strD10Fail "5" :: IO D10
+-- [d10|5|]
+--
+-- >>> strD10Fail "a" :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10Fail "58" :: IO D10
+-- *** Exception: user error (d10 must be a single character)
+
+strD10Fail :: MonadFail m => String -> m D10
+strD10Fail [x]         =  charD10Fail x
+strD10Fail _           =  fail "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'fail'
+-- with an error message otherwise.
+--
+-- 'strD10ListMaybe' is a specialized version of this function.
+--
+-- >>> strD10ListFail "5" :: IO [D10]
+-- [d10list|5|]
+--
+-- >>> strD10ListFail "a" :: IO [D10]
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10ListFail "58" :: IO [D10]
+-- [d10list|58|]
+
+strD10ListFail :: MonadFail m => String -> m [D10]
+strD10ListFail = traverse charD10Fail
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'fail' with an error message otherwise.
+--
+-- 'natD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> natD10Fail 5 :: IO D10
+-- [d10|5|]
+--
+-- >>> natD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be less than 10)
+
+natD10Fail :: MonadFail m => Natural -> m D10
+natD10Fail x =
+    case (natD10Maybe x) of
+        Just y  -> return y
+        Nothing -> fail "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'integerD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integerD10Fail 5 :: IO D10
+-- [d10|5|]
+--
+-- >>> integerD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integerD10Fail (-5) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integerD10Fail :: MonadFail m => Integer -> m D10
+integerD10Fail x =
+    case (integerD10Maybe x) of
+        Just y  -> return y
+        Nothing -> fail "d10 must be between 0 and 9"
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'intD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> intD10Fail 5 :: IO D10
+-- [d10|5|]
+--
+-- >>> intD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> intD10Fail (-5) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+intD10Fail :: MonadFail m => Int -> m D10
+intD10Fail x =
+    case (intD10Maybe x) of
+        Just y  ->  return y
+        Nothing ->  fail "d10 must be between 0 and 9"
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'fail'
+-- with an error message otherwise.
+--
+-- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
+-- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
+-- 'intD10Fail' are all specialized versions of this function.
+--
+-- >>> integralD10Fail (5 :: Integer) :: IO D10
+-- [d10|5|]
+--
+-- >>> integralD10Fail (12 :: Integer) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integralD10Fail ((-5) :: Integer) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integralD10Fail :: (Integral a, MonadFail m) => a -> m D10
+integralD10Fail x = integerD10Fail (toInteger x)
diff --git a/src/D10/Char/Quotes.hs b/src/D10/Char/Quotes.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Quotes.hs
@@ -0,0 +1,104 @@
+{-# language Safe #-}
+
+module D10.Char.Quotes where
+
+import D10.Char.Conversions
+import D10.Char.Splices
+
+import Control.Monad ((>=>))
+import Control.Monad.Fail (MonadFail (fail))
+import Language.Haskell.TH.Quote (QuasiQuoter (..))
+import Prelude hiding (fail)
+
+-- | A single base-10 digit.
+--
+-- This quasi-quoter, when used as an expression, produces a
+-- value of type 'D10'.
+--
+-- >>> d10Nat [d10|5|]
+-- 5
+--
+-- >>> d10Nat [d10|a|]
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- >>> d10Nat [d10|58|]
+-- ...
+-- ... d10 must be a single character
+-- ...
+--
+-- This quasi-quoter can also be used as a pattern.
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just [d10|4|] -> "A"
+--         Just [d10|5|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- "B"
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just [d10|x|] -> "A"
+--         Just [d10|5|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+
+d10 :: QuasiQuoter
+d10 = QuasiQuoter
+    { quoteExp  = strD10Fail >=> (d10Exp . d10Integer)
+    , quotePat  = strD10Fail >=> (d10Pat . d10Integer)
+    , quoteType = \_ -> fail "d10 cannot be used in a type context"
+    , quoteDec  = \_ -> fail "d10 cannot be used in a declaration context"
+    }
+
+-- | A list of base-10 digits.
+--
+-- This quasi-quoter, when used as an expression, produces a
+-- value of type @['D10']@.
+--
+-- >>> d10Nat <$> [d10list||]
+-- []
+--
+-- >>> d10Nat <$> [d10list|5|]
+-- [5]
+--
+-- >>> d10Nat <$> [d10list|58|]
+-- [5,8]
+--
+-- >>> d10Nat <$> [d10list|a|]
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- This quasi-quoter can also be used as a pattern.
+--
+-- >>> :{
+--       case [d10list|56|] of
+--         [d10list|41|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- "B"
+--
+-- >>> :{
+--       case [d10list|56|] of
+--         [d10list|4x|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+
+d10list :: QuasiQuoter
+d10list = QuasiQuoter
+    { quoteExp  = d10ListExp
+    , quotePat  = d10ListPat
+    , quoteType = \_ -> fail "d10list cannot be used in a type context"
+    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
+    }
diff --git a/src/D10/Char/Splices.hs b/src/D10/Char/Splices.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Splices.hs
@@ -0,0 +1,102 @@
+{-# language Trustworthy, TemplateHaskell #-}
+
+module D10.Char.Splices
+    (
+    -- * Expressions
+      d10Exp, d10ListExp
+    -- * Patterns
+    , d10Pat, d10ListPat
+    ) where
+
+import D10.Char.Conversions
+import D10.Char.Unsafe
+
+import Control.Monad ((>=>))
+import Language.Haskell.TH.Lib (appE, charL, conE, litE, litP)
+import Language.Haskell.TH.Syntax (Exp (..), Pat (..), Q)
+
+-- | Produces an expression of type 'D10' that can be used
+-- in a Template Haskell splice.
+--
+-- >>> d10Nat $(d10Exp 5)
+-- 5
+--
+-- >>> d10Nat $(d10Exp 12)
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- You may also be interested in 'd10', a quasi-quoter which
+-- does something similar.
+
+d10Exp :: Integer -> Q Exp
+d10Exp = integerD10Fail >=> d10Exp'
+
+d10Exp' :: D10 -> Q Exp
+d10Exp' x = conE 'D10_Unsafe `appE` litE (charL (d10Char x))
+
+-- | Produces an expression of type @['D10']@ that can be used
+-- in a Template Haskell splice.
+--
+-- >>> d10Nat <$> $(d10ListExp "")
+-- []
+--
+-- >>> d10Nat <$> $(d10ListExp "5")
+-- [5]
+--
+-- >>> d10Nat <$> $(d10ListExp "58")
+-- [5,8]
+--
+-- >>> d10Nat <$> $(d10ListExp "a")
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- You may also be interested in 'd10list', a quasi-quoter which
+-- does something similar.
+
+d10ListExp :: String -> Q Exp
+d10ListExp = strD10ListFail >=> d10ListExp'
+
+d10ListExp' :: [D10] -> Q Exp
+d10ListExp' =
+  foldr
+    (\x e -> conE '(:) `appE` d10Exp' x `appE` e)
+    (conE '[])
+
+---------------------------------------------------
+
+-- | Produces a pattern that can be used in a splice
+-- to match a particular 'D10' value.
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just $(d10Pat 4) -> "A"
+--         Just $(d10Pat 5) -> "B"
+--         _                -> "C"
+-- >>> :}
+-- "B"
+--
+-- You may wish to use the 'd10' quasi-quoter instead.
+
+d10Pat :: Integer -> Q Pat
+d10Pat = integerD10Fail >=> d10Pat'
+
+d10Pat' :: D10 -> Q Pat
+d10Pat' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ charL x) |]
+
+-- | Produces a pattern that can be used in a splice
+-- to match a particular list of 'D10' values.
+--
+-- >>> :{
+--       case (strD10ListMaybe "56") of
+--         Just $(d10ListPat "42") -> "A"
+--         Just $(d10ListPat "56") -> "B"
+--         _                       -> "C"
+-- >>> :}
+-- "B"
+--
+-- You may wish to use the 'd10list' quasi-quoter instead.
+
+d10ListPat :: String -> Q Pat
+d10ListPat = strD10ListFail >=> foldr (\x p -> [p| $(d10Pat' x) : $(p) |]) [p| [] |]
diff --git a/src/D10/Char/Type.hs b/src/D10/Char/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Type.hs
@@ -0,0 +1,5 @@
+{-# language Trustworthy #-}
+
+module D10.Char.Type (D10) where
+
+import D10.Char.Unsafe
diff --git a/src/D10/Char/Unsafe.hs b/src/D10/Char/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Char/Unsafe.hs
@@ -0,0 +1,65 @@
+{-# language Unsafe, GeneralizedNewtypeDeriving #-}
+
+module D10.Char.Unsafe (D10 (D10_Unsafe)) where
+
+import qualified D10.Predicate as Predicate
+
+import Data.Char (chr, ord)
+import Data.Monoid (Endo (..))
+
+---------------------------------------------------
+
+-- | A 'Char' value between @'0'@ and @'9'@
+newtype D10 =
+    D10_Unsafe Char
+      -- ^ The constructor's name include the word "unsafe" as a reminder
+      --   that you should generally avoid using it directly, because it
+      --   allows constructing invalid 'D10' values.
+    deriving (Eq, Ord)
+
+---------------------------------------------------
+
+instance Bounded D10
+  where
+    minBound = D10_Unsafe '0'
+    maxBound = D10_Unsafe '9'
+
+---------------------------------------------------
+
+instance Enum D10
+  where
+    fromEnum :: D10 -> Int
+    fromEnum (D10_Unsafe x) = ord x - ord '0'
+
+    toEnum :: Int -> D10
+    toEnum x | Predicate.isD10Int x = D10_Unsafe (chr (x + ord '0'))
+             | otherwise = error "d10 must be between 0 and 9"
+
+    enumFrom :: D10 -> [D10]
+    enumFrom x = enumFromTo x maxBound
+
+    enumFromThen :: D10 -> D10 -> [D10]
+    enumFromThen x y = enumFromThenTo x y bound
+      where
+        bound | fromEnum y >= fromEnum x = maxBound
+              | otherwise                = minBound
+
+    succ :: D10 -> D10
+    succ (D10_Unsafe '9') = error "D10 overflow"
+    succ (D10_Unsafe x) = D10_Unsafe (succ x)
+
+    pred :: D10 -> D10
+    pred (D10_Unsafe '0') = error "D10 underflow"
+    pred (D10_Unsafe x) = D10_Unsafe (pred x)
+
+---------------------------------------------------
+
+instance Show D10 where
+    showsPrec _ x = showString "[d10|"     . showsChar x . showString "|]"
+    showList xs   = showString "[d10list|" . showsStr xs . showString "|]"
+
+showsChar :: D10 -> ShowS
+showsChar (D10_Unsafe x) = showChar x
+
+showsStr :: [D10] -> ShowS
+showsStr = appEndo . foldMap (Endo . showsChar)
diff --git a/src/D10/Num.hs b/src/D10/Num.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num.hs
@@ -0,0 +1,138 @@
+{-# language Safe #-}
+
+{- |
+
+Defines a 'D10' type as a newtype for any type with an
+instance of the 'Num' class, where the values are restricted
+to numbers between @'fromInteger' 0@ and @'fromInteger' 9@.
+
+This module provides many functions for constructing 'D10'
+values, including:
+
+  * @'integerD10Maybe' :: 'Num' a => 'Integer' -> 'Maybe' ('D10' a)@
+  * @'integerMod10' :: 'Num' a => 'Integer' -> 'D10' a@
+
+There are also several ways to safely write 'D10' literals
+using Template Haskell:
+
+  * With the @QuasiQuotes@ GHC extension enabled, you can write
+    use the quasi-quoters 'd10' and 'd10list'.
+  * With the @TemplateHaskell@ GHC extension enabled, you can
+    splice expressions produced by 'd10Exp' and 'd10ListExp'.
+
+-}
+
+module D10.Num
+    (
+    -- * Related modules
+    -- $modules
+
+    -- * Type
+      D10
+
+    -- ** Bounded
+    -- $bounded
+
+    -- ** Enum
+    -- $enum
+
+    -- ** Show
+    -- $show
+
+    -- * Quasi-quoters
+    , d10, d10list
+
+    -- * Splices
+    -- ** Expressions
+    , d10Exp, d10ListExp
+    -- ** Patterns
+    , d10Pat, d10ListPat
+
+    -- * Conversions
+    -- ** D10 / Char
+    , d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- ** D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- ** [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- ** D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail
+    , natMod10
+    -- ** D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- ** D10 / Int
+    , d10Int, intD10Maybe, intD10Either, intD10Fail
+    , intMod10
+    -- ** D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+
+    ) where
+
+import D10.Num.Conversions
+import D10.Num.Type
+import D10.Num.Quotes
+import D10.Num.Splices
+
+{- $modules
+
+Additional functions related to this 'D10' type may be found in:
+
+  * "D10.Num.Arithmetic"
+
+The contents of the following modules are re-exported here:
+
+  * "D10.Num.Conversions"
+  * "D10.Num.Quotes"
+  * "D10.Num.Splices"
+  * "D10.Num.Type"
+
+The unsafe constructor for 'D10' can be found in:
+
+  * "D10.Num.Unsafe"
+
+The following modules define @D10@ types in different ways
+but are otherwise very similar to this one:
+
+  * "D10.Char"
+  * "D10.Safe"
+
+-}
+
+{- $bounded
+
+>>> minBound :: D10 Integer
+[d10|0|]
+
+>>> maxBound :: D10 Integer
+[d10|9|]
+
+-}
+
+{- $enum
+
+>>> [ [d10|5|] .. ]
+[d10list|56789|]
+
+>>> [ [d10|4|] .. [d10|7|] ]
+[d10list|4567|]
+
+>>> [ [d10|5|], [d10|4|] .. ]
+[d10list|543210|]
+
+>>> [ [d10|1|], [d10|3|] .. ]
+[d10list|13579|]
+
+>>> [ minBound .. maxBound ] :: [D10 Integer]
+[d10list|0123456789|]
+
+-}
+
+{- $show
+
+'show' shows base-10 digits using the quasiquoters defined
+in this module. A single digit is displayed using 'd10'.
+A list of digits is displayed using 'd10list'.
+
+-}
diff --git a/src/D10/Num/Arithmetic.hs b/src/D10/Num/Arithmetic.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Arithmetic.hs
@@ -0,0 +1,41 @@
+{-# language Safe #-}
+
+module D10.Num.Arithmetic where
+
+import D10.Num (D10, d10Int, intMod10)
+
+import qualified Prelude as P
+import Prelude (Integral)
+
+-- | Addition modulo 10.
+--
+-- >>> [d10|2|] + [d10|3|]
+-- [d10|5|]
+--
+-- >>> [d10|6|] + [d10|7|]
+-- [d10|3|]
+
+(+) :: Integral a => D10 a -> D10 a -> D10 a
+x + y = intMod10 (d10Int x P.+ d10Int y)
+
+-- | Subtraction modulo 10.
+--
+-- >>> [d10|7|] - [d10|5|]
+-- [d10|2|]
+--
+-- >>> [d10|3|] - [d10|7|]
+-- [d10|6|]
+
+(-) :: Integral a => D10 a -> D10 a -> D10 a
+x - y = intMod10 (d10Int x P.- d10Int y)
+
+-- | Multiplication modulo 10.
+--
+-- >>> [d10|2|] * [d10|4|]
+-- [d10|8|]
+--
+-- >>> [d10|7|] * [d10|8|]
+-- [d10|6|]
+
+(*) :: Integral a => D10 a -> D10 a -> D10 a
+x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/src/D10/Num/Conversions.hs b/src/D10/Num/Conversions.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Conversions.hs
@@ -0,0 +1,547 @@
+{-# language Trustworthy #-}
+
+module D10.Num.Conversions
+    (
+    -- * D10 / Char
+      d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- * D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- * [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- * D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail
+    , natMod10
+    -- * D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- * D10 / Int
+    , d10Int, intD10Maybe, intD10Either, intD10Fail
+    , intMod10
+    -- * D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+    ) where
+
+import D10.Num.Unsafe (D10(..))
+import D10.Predicate (isD10Char, isD10Int, isD10Integer, isD10Nat)
+
+-- base
+import Control.Monad.Fail (MonadFail (fail))
+import Data.Char          (chr, ord)
+import Numeric.Natural    (Natural)
+import Prelude            hiding (fail)
+
+import qualified Prelude as P
+
+-- | Convert a 'D10' to its underlying 'Char' representation.
+--
+-- >>> d10Char [d10|7|]
+-- '7'
+
+d10Char :: Integral a => D10 a -> Char
+d10Char (D10_Unsafe x) = chr (ord '0' P.+ fromIntegral x)
+
+-- | Convert a 'D10' to a 'String'.
+--
+-- @'d10Str' x = ['d10Char' x]@
+--
+-- >>> d10Str [d10|7|]
+-- "7"
+
+d10Str :: Integral a => D10 a -> String
+d10Str x = [d10Char x]
+
+-- | Convert a 'D10' to a 'Natural'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Nat [d10|7|]
+-- 7
+
+d10Nat :: Integral a => D10 a -> Natural
+d10Nat = d10Num
+
+-- | Convert a 'D10' to an 'Integer'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Integer [d10|7|]
+-- 7
+
+d10Integer :: Integral a => D10 a -> Integer
+d10Integer = d10Num
+
+-- | Convert a 'D10' to an 'Int'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Int [d10|7|]
+-- 7
+
+d10Int :: Integral a => D10 a -> Int
+d10Int = d10Num
+
+-- | Convert a 'D10' to any kind of number with a 'Num' instance.
+--
+-- Specialized versions of this function include 'd10Nat',
+-- 'd10Integer', and 'd10Int'.
+--
+-- >>> d10Num [d10|7|] :: Integer
+-- 7
+
+d10Num :: (Integral b, Num a) => D10 b -> a
+d10Num (D10_Unsafe x) = fromIntegral x
+
+---------------------------------------------------
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> natMod10 56 :: D10 Int
+-- [d10|6|]
+
+natMod10 :: Num a => Natural -> D10 a
+natMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> integerMod10 56 :: D10 Int
+-- [d10|6|]
+--
+-- >>> integerMod10 (-56) :: D10 Int
+-- [d10|4|]
+
+integerMod10 :: Num a => Integer -> D10 a
+integerMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> intMod10 56 :: D10 Int
+-- [d10|6|]
+--
+-- >>> intMod10 (-56) :: D10 Int
+-- [d10|4|]
+
+intMod10 :: Num a => Int -> D10 a
+intMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given number
+-- (whose type must have an instance of the 'Integral' class).
+--
+-- Specialized versions of this function include 'natMod10',
+-- 'integerMod10', and 'intMod10'.
+--
+-- >>> integralMod10 (56 :: Integer) :: D10 Int
+-- [d10|6|]
+--
+-- >>> integralMod10 ((-56) :: Integer) :: D10 Int
+-- [d10|4|]
+
+integralMod10 :: (Num b, Integral a) => a -> D10 b
+integralMod10 x = D10_Unsafe (fromIntegral (x `mod` 10))
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
+--
+-- 'charD10Fail' is a more general version of this function.
+--
+-- >>> charD10Maybe '5'
+-- Just [d10|5|]
+--
+-- >>> charD10Maybe 'a'
+-- Nothing
+
+charD10Maybe :: Num a => Char -> Maybe (D10 a)
+charD10Maybe x
+        | isD10Char x  =  Just (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
+        | otherwise    =  Nothing
+
+-- | Convert a 'String' to a 'D10' if it consists of exactly one
+-- character and that character is within the range @'0'@ to @'9'@,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
+--
+-- 'strD10Fail' is a more general version of this function.
+--
+-- >>> strD10Maybe "5"
+-- Just [d10|5|]
+--
+-- >>> strD10Maybe "a"
+-- Nothing
+--
+-- >>> strD10Maybe "58"
+-- Nothing
+
+strD10Maybe :: Num a => String -> Maybe (D10 a)
+strD10Maybe [x] = charD10Maybe x
+strD10Maybe _   = Nothing
+
+-- | Convert a 'String' to a list of 'D10' if all of the characters
+-- in the string are within the range @'0'@ to @'9'@, or produce
+-- 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
+--
+-- 'strD10ListFail' is a more general version of this function.
+--
+-- >>> strD10ListMaybe "5"
+-- Just [d10list|5|]
+--
+-- >>> strD10ListMaybe "a"
+-- Nothing
+--
+-- >>> strD10ListMaybe "58"
+-- Just [d10list|58|]
+
+strD10ListMaybe :: Num a => String -> Maybe [D10 a]
+strD10ListMaybe = traverse charD10Maybe
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> natD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> natD10Maybe 12
+-- Nothing
+
+natD10Maybe :: Num a => Natural -> Maybe (D10 a)
+natD10Maybe x
+        | isD10Nat x  =  Just (D10_Unsafe (fromIntegral x))
+        | otherwise   =  Nothing
+
+-- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> integerD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> integerD10Maybe 12
+-- Nothing
+--
+-- >>> integerD10Maybe (-5)
+-- Nothing
+
+integerD10Maybe :: Num a => Integer -> Maybe (D10 a)
+integerD10Maybe x
+        | isD10Integer x  =  Just (D10_Unsafe (fromIntegral x))
+        | otherwise       =  Nothing
+
+-- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> intD10Maybe 5
+-- Just [d10|5|]
+--
+-- >>> intD10Maybe 12
+-- Nothing
+--
+-- >>> intD10Maybe (-5)
+-- Nothing
+
+intD10Maybe :: Num a => Int -> Maybe (D10 a)
+intD10Maybe x
+        | isD10Int x  =  Just (D10_Unsafe (fromIntegral x))
+        | otherwise   =  Nothing
+
+-- | Construct a 'D10' from any kind of number with an 'Integral'
+-- instance, or produce 'Nothing' if the number falls outside the
+-- range 0 to 9.
+--
+-- @'D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
+--
+-- Specialized versions of this function include 'natD10Maybe',
+-- 'integerD10Maybe', and 'intD10Maybe'.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integralD10Maybe (5 :: Integer)
+-- Just [d10|5|]
+--
+-- >>> integralD10Maybe (12 :: Integer)
+-- Nothing
+--
+-- >>> integralD10Maybe ((-5) :: Integer)
+-- Nothing
+
+integralD10Maybe :: (Num b, Integral a) => a -> Maybe (D10 b)
+integralD10Maybe x = integerD10Maybe (toInteger x)
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> charD10Either '5'
+-- Right [d10|5|]
+--
+-- >>> charD10Either 'a'
+-- Left "d10 must be between 0 and 9"
+
+charD10Either :: Num a => Char -> Either String (D10 a)
+charD10Either x
+        | isD10Char x  =  Right (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
+        | otherwise    =  Left "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> strD10Either "5"
+-- Right [d10|5|]
+--
+-- >>> strD10Either "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10Either "58"
+-- Left "d10 must be a single character"
+
+strD10Either :: Num a => String -> Either String (D10 a)
+strD10Either [x]         =  charD10Either x
+strD10Either _           =  Left "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> strD10ListEither "5"
+-- Right [d10list|5|]
+--
+-- >>> strD10ListEither "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10ListEither "58"
+-- Right [d10list|58|]
+
+strD10ListEither :: Num a => String -> Either String [D10 a]
+strD10ListEither = traverse charD10Either
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'Left' with an error message otherwise.
+--
+-- >>> natD10Either 5
+-- Right [d10|5|]
+--
+-- >>> natD10Either 12
+-- Left "d10 must be less than 10"
+
+natD10Either :: Num a => Natural -> Either String (D10 a)
+natD10Either x =
+    case (natD10Maybe x) of
+        Just y  -> Right y
+        Nothing -> Left "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> integerD10Either 5
+-- Right [d10|5|]
+--
+-- >>> integerD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integerD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+integerD10Either :: Num a => Integer -> Either String (D10 a)
+integerD10Either x =
+    case (integerD10Maybe x) of
+        Just y  -> Right y
+        Nothing -> Left "d10 must be between 0 and 9"
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> intD10Either 5
+-- Right [d10|5|]
+--
+-- >>> intD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> intD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+intD10Either :: Num a => Int -> Either String (D10 a)
+intD10Either x =
+    case (intD10Maybe x) of
+        Just y  ->  Right y
+        Nothing ->  Left "d10 must be between 0 and 9"
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> integralD10Either (5 :: Integer)
+-- Right [d10|5|]
+--
+-- >>> integralD10Either (12 :: Integer)
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integralD10Either ((-5) :: Integer)
+-- Left "d10 must be between 0 and 9"
+
+integralD10Either :: (Num b, Integral a) => a -> Either String (D10 b)
+integralD10Either x = integerD10Either (toInteger x)
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'charD10Maybe' is a specialized version of this function.
+--
+-- >>> charD10Fail '5' :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> charD10Fail 'a' :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+charD10Fail :: (Num a, MonadFail m) => Char -> m (D10 a)
+charD10Fail x
+        | isD10Char x  =  return (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
+        | otherwise    =  fail "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'strD10Maybe' is a specialized version of this function.
+--
+-- >>> strD10Fail "5" :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> strD10Fail "a" :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10Fail "58" :: IO (D10 Int)
+-- *** Exception: user error (d10 must be a single character)
+
+strD10Fail :: (Num a, MonadFail m) => String -> m (D10 a)
+strD10Fail [x]         =  charD10Fail x
+strD10Fail _           =  fail "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'fail'
+-- with an error message otherwise.
+--
+-- 'strD10ListMaybe' is a specialized version of this function.
+--
+-- >>> strD10ListFail "5" :: IO [D10 Int]
+-- [d10list|5|]
+--
+-- >>> strD10ListFail "a" :: IO [D10 Int]
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10ListFail "58" :: IO [D10 Int]
+-- [d10list|58|]
+
+strD10ListFail :: (Num a, MonadFail m) => String -> m [D10 a]
+strD10ListFail = traverse charD10Fail
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'fail' with an error message otherwise.
+--
+-- 'natD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> natD10Fail 5 :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> natD10Fail 12 :: IO (D10 Int)
+-- *** Exception: user error (d10 must be less than 10)
+
+natD10Fail :: (Num a, MonadFail m) => Natural -> m (D10 a)
+natD10Fail x =
+    case (natD10Maybe x) of
+        Just y  -> return y
+        Nothing -> fail "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'integerD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integerD10Fail 5 :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> integerD10Fail 12 :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integerD10Fail (-5) :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integerD10Fail :: (Num a, MonadFail m) => Integer -> m (D10 a)
+integerD10Fail x =
+    case (integerD10Maybe x) of
+        Just y  -> return y
+        Nothing -> fail "d10 must be between 0 and 9"
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'intD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> intD10Fail 5 :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> intD10Fail 12 :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> intD10Fail (-5) :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+intD10Fail :: (Num a, MonadFail m) => Int -> m (D10 a)
+intD10Fail x =
+    case (intD10Maybe x) of
+        Just y  ->  return y
+        Nothing ->  fail "d10 must be between 0 and 9"
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'fail'
+-- with an error message otherwise.
+--
+-- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
+-- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
+-- 'intD10Fail' are all specialized versions of this function.
+--
+-- >>> integralD10Fail (5 :: Integer) :: IO (D10 Int)
+-- [d10|5|]
+--
+-- >>> integralD10Fail (12 :: Integer) :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integralD10Fail ((-5) :: Integer) :: IO (D10 Int)
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integralD10Fail :: (Num b, Integral a, MonadFail m) => a -> m (D10 b)
+integralD10Fail x = integerD10Fail (toInteger x)
diff --git a/src/D10/Num/Quotes.hs b/src/D10/Num/Quotes.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Quotes.hs
@@ -0,0 +1,104 @@
+{-# language Safe #-}
+
+module D10.Num.Quotes where
+
+import D10.Num.Conversions
+import D10.Num.Splices
+
+import Control.Monad ((>=>))
+import Control.Monad.Fail (MonadFail (fail))
+import Prelude hiding (fail)
+import Language.Haskell.TH.Quote (QuasiQuoter (..))
+
+-- | A single base-10 digit.
+--
+-- This quasi-quoter, when used as an expression, produces a
+-- value of type @'D10' a@.
+--
+-- >>> d10Nat [d10|5|]
+-- 5
+--
+-- >>> d10Nat [d10|a|]
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- >>> d10Nat [d10|58|]
+-- ...
+-- ... d10 must be a single character
+-- ...
+--
+-- This quasi-quoter can also be used as a pattern.
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just [d10|4|] -> "A"
+--         Just [d10|5|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- "B"
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just [d10|x|] -> "A"
+--         Just [d10|5|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+
+d10 :: QuasiQuoter
+d10 = QuasiQuoter
+    { quoteExp  = strD10Fail >=> (d10Exp . d10Integer @Integer)
+    , quotePat  = strD10Fail >=> (d10Pat . d10Integer @Integer)
+    , quoteType = \_ -> fail "d10 cannot be used in a type context"
+    , quoteDec  = \_ -> fail "d10 cannot be used in a declaration context"
+    }
+
+-- | A list of base-10 digits.
+--
+-- This quasi-quoter, when used as an expression, produces a
+-- value of type @['D10' a]@.
+--
+-- >>> d10Nat <$> [d10list||]
+-- []
+--
+-- >>> d10Nat <$> [d10list|5|]
+-- [5]
+--
+-- >>> d10Nat <$> [d10list|58|]
+-- [5,8]
+--
+-- >>> d10Nat <$> [d10list|a|]
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- This quasi-quoter can also be used as a pattern.
+--
+-- >>> :{
+--       case [d10list|56|] of
+--         [d10list|41|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- "B"
+--
+-- >>> :{
+--       case [d10list|56|] of
+--         [d10list|4x|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+
+d10list :: QuasiQuoter
+d10list = QuasiQuoter
+    { quoteExp  = d10ListExp
+    , quotePat  = d10ListPat
+    , quoteType = \_ -> fail "d10list cannot be used in a type context"
+    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
+    }
diff --git a/src/D10/Num/Splices.hs b/src/D10/Num/Splices.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Splices.hs
@@ -0,0 +1,108 @@
+{-# language Trustworthy, TemplateHaskell #-}
+
+module D10.Num.Splices
+    (
+    -- * Expressions
+      d10Exp, d10ListExp
+    -- * Patterns
+    , d10Pat, d10ListPat
+    ) where
+
+import D10.Num.Conversions
+import D10.Num.Unsafe (D10(..))
+
+import Control.Monad ((>=>))
+import Language.Haskell.TH.Lib (appE, conE, integerL, litE, litP, varE)
+import Language.Haskell.TH.Syntax (Exp (..), Pat (..), Q)
+
+-- | Produces an expression of type @'D10' a@ that can be used
+-- in a Template Haskell splice.
+--
+-- >>> d10Nat $(d10Exp 5)
+-- 5
+--
+-- >>> d10Nat $(d10Exp 12)
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- You may also be interested in 'd10', a quasi-quoter which
+-- does something similar.
+
+d10Exp :: Integer -> Q Exp
+d10Exp = integerD10Fail >=> d10Exp'
+
+d10Exp' :: D10 Integer -> Q Exp
+d10Exp' (D10_Unsafe x) = conE 'D10_Unsafe `appE` (varE 'fromInteger `appE` litE (integerL x))
+
+-- | Produces an expression of type @['D10' a]@ that can be used
+-- in a Template Haskell splice.
+--
+-- >>> d10Nat <$> $(d10ListExp "")
+-- []
+--
+-- >>> d10Nat <$> $(d10ListExp "5")
+-- [5]
+--
+-- >>> d10Nat <$> $(d10ListExp "58")
+-- [5,8]
+--
+-- >>> d10Nat <$> $(d10ListExp "a")
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- You may also be interested in 'd10list', a quasi-quoter which
+-- does something similar.
+
+d10ListExp :: String -> Q Exp
+d10ListExp = strD10ListFail >=> d10ListExp'
+
+d10ListExp' :: [D10 Integer] -> Q Exp
+d10ListExp' =
+  foldr
+    (\x e -> conE '(:) `appE` d10Exp' x `appE` e)
+    (conE '[])
+
+---------------------------------------------------
+
+-- | Produces a pattern that can be used in a splice
+-- to match a particular @'D10' a@ value.
+--
+-- >>> :{
+--       case (charD10Maybe '5') of
+--         Just $(d10Pat 4) -> "A"
+--         Just $(d10Pat 5) -> "B"
+--         _                -> "C"
+-- >>> :}
+-- "B"
+--
+-- You may wish to use the 'd10' quasi-quoter instead.
+
+d10Pat :: Integer -> Q Pat
+d10Pat = integerD10Fail >=> d10Pat'
+
+d10Pat' :: D10 Integer -> Q Pat
+d10Pat' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ integerL x) |]
+
+d10Pat'' :: Integral a => D10 a -> Q Pat
+d10Pat'' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ integerL $ toInteger x) |]
+
+-- | Produces a pattern that can be used in a splice
+-- to match a particular list of @'D10' a@ values.
+--
+-- >>> :{
+--       case (strD10ListMaybe "56") of
+--         Just $(d10ListPat "42") -> "A"
+--         Just $(d10ListPat "56") -> "B"
+--         _                       -> "C"
+-- >>> :}
+-- "B"
+--
+-- You may wish to use the 'd10list' quasi-quoter instead.
+
+d10ListPat :: String -> Q Pat
+d10ListPat = strD10ListFail >=> d10ListPat'
+
+d10ListPat' :: [D10 Integer] -> Q Pat
+d10ListPat' = foldr (\x p -> [p| $(d10Pat'' x) : $(p) |]) [p| [] |]
diff --git a/src/D10/Num/Type.hs b/src/D10/Num/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Type.hs
@@ -0,0 +1,5 @@
+{-# language Trustworthy #-}
+
+module D10.Num.Type (D10) where
+
+import D10.Num.Unsafe
diff --git a/src/D10/Num/Unsafe.hs b/src/D10/Num/Unsafe.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Num/Unsafe.hs
@@ -0,0 +1,64 @@
+{-# language Unsafe, GeneralizedNewtypeDeriving #-}
+
+module D10.Num.Unsafe (D10 (..)) where
+
+import qualified D10.Predicate as Predicate
+
+import Data.Char (chr, ord)
+import Data.Monoid (Endo (..))
+
+---------------------------------------------------
+
+-- | A value of some numeric type @a@ between
+-- @'fromInteger' 0@ and @'fromInteger' 9@.
+newtype D10 a =
+    D10_Unsafe a
+      -- ^ The constructor's name include the word "unsafe" as a reminder
+      --   that you should generally avoid using it directly, because it
+      --   allows constructing invalid 'D10' values.
+    deriving (Eq, Ord)
+
+---------------------------------------------------
+
+instance Num a => Bounded (D10 a)
+  where
+    minBound = D10_Unsafe 0
+    maxBound = D10_Unsafe 9
+
+---------------------------------------------------
+
+instance Integral a => Enum (D10 a)
+  where
+    fromEnum :: D10 a -> Int
+    fromEnum (D10_Unsafe x) = fromIntegral x
+
+    toEnum :: Int -> D10 a
+    toEnum x | Predicate.isD10Int x = D10_Unsafe (fromIntegral x)
+             | otherwise = error "d10 must be between 0 and 9"
+
+    enumFrom :: D10 a -> [D10 a]
+    enumFrom x = enumFromTo x maxBound
+
+    enumFromThen :: D10 a -> D10 a -> [D10 a]
+    enumFromThen x y = enumFromThenTo x y bound
+      where
+        bound | fromEnum y >= fromEnum x = maxBound
+              | otherwise                = minBound
+
+    succ (D10_Unsafe 9) = error "D10 overflow"
+    succ (D10_Unsafe x) = D10_Unsafe (succ x)
+
+    pred (D10_Unsafe 0) = error "D10 underflow"
+    pred (D10_Unsafe x) = D10_Unsafe (pred x)
+
+---------------------------------------------------
+
+instance Integral a => Show (D10 a) where
+    showsPrec _ x = showString "[d10|"     . showsChar x . showString "|]"
+    showList xs   = showString "[d10list|" . showsStr xs . showString "|]"
+
+showsChar :: Integral a => D10 a -> ShowS
+showsChar (D10_Unsafe x) = showChar $ chr (ord '0' + fromIntegral x)
+
+showsStr :: Integral a => [D10 a] -> ShowS
+showsStr = appEndo . foldMap (Endo . showsChar)
diff --git a/src/D10/Predicate.hs b/src/D10/Predicate.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Predicate.hs
@@ -0,0 +1,39 @@
+{-# language Safe #-}
+
+-- | Functions to test whether values of various
+-- types represent digits in the range /0/ to /9/.
+module D10.Predicate where
+
+import Numeric.Natural (Natural)
+
+-- | Determines whether a 'Char' is in the range @'0'@ to @'9'@.
+isD10Char :: Char -> Bool
+isD10Char x = x >= '0' && x <= '9'
+
+-- | Determines whether a 'String' consists of a single character
+-- and that character is within the range @'0'@ to @'9'@.
+isD10Str :: String -> Bool
+isD10Str [x] = isD10Char x
+isD10Str _   = False
+
+-- | Determines whether a 'String' consists entirely of characters
+-- that are within the range @'0'@ to @'9'@.
+isD10ListStr :: String -> Bool
+isD10ListStr = all isD10Char
+
+-- | Determines whether a 'Natural' is in the range 0 to 9.
+isD10Nat :: Natural -> Bool
+isD10Nat x = x <= 9
+
+-- | Determines whether an 'Integer' is in the range 0 to 9.
+isD10Integer :: Integer -> Bool
+isD10Integer x = x >= 0 && x <= 9
+
+-- | Determines whether an 'Int' is in the range 0 to 9.
+isD10Int :: Int -> Bool
+isD10Int x = x >= 0 && x <= 9
+
+-- | Determines whether a number whose type has an 'Integral'
+-- instance is in the range 0 to 9.
+isD10Integral :: Integral a => a -> Bool
+isD10Integral x = isD10Integer (toInteger x)
diff --git a/src/D10/Safe.hs b/src/D10/Safe.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe.hs
@@ -0,0 +1,110 @@
+{-# language Safe #-}
+
+{- |
+
+Defines a 'D10' type as
+@'D0' | 'D1' | 'D2' | 'D3' | 'D4' | 'D5' | 'D6' | 'D7' | 'D8' | 'D9'@.
+
+-}
+
+module D10.Safe
+    (
+    -- * Related modules
+    -- $modules
+
+    -- * Type
+      D10 (..)
+
+    -- ** Bounded
+    -- $bounded
+
+    -- ** Enum
+    -- $enum
+
+    -- * Quasi-quoters
+    , d10list
+
+    -- * Splices
+    -- ** Expressions
+    , d10ListExp
+    -- ** Patterns
+    , d10ListPat
+
+    -- * Conversions
+    -- ** D10 / Char
+    , d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- ** D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- ** [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- ** D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail, natMod10
+    -- ** D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- ** D10 / Int
+    , d10Int, intD10Maybe, intD10Either, intD10Fail, intMod10
+    -- ** D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+    ) where
+
+import D10.Safe.Conversions
+import D10.Safe.Quotes
+import D10.Safe.Splices
+import D10.Safe.Type
+
+{- $modules
+
+Additional functions related to this 'D10' type may be found in:
+
+  * "D10.Safe.Arithmetic"
+
+The contents of the following modules are re-exported here:
+
+  * "D10.Safe.Type"
+  * "D10.Safe.Conversions"
+  * "D10.Safe.Quotes"
+  * "D10.Safe.Splices"
+
+The following modules define @D10@ types in different ways
+but are otherwise very similar to this one:
+
+  * "D10.Char"
+  * "D10.Num"
+
+This module is called "safe" because, in contrast with the
+alternative representations of a digit defined in the other
+modules, this 'D10' type does not include any possibility
+of representing an invalid non-digit value.
+
+-}
+
+{- $bounded
+
+-- >>> minBound :: D10
+-- D0
+--
+-- >>> maxBound :: D10
+-- D9
+
+-}
+
+{- $enum
+
+>>> [ D5 .. ]
+[D5,D6,D7,D8,D9]
+
+>>> [ D4 .. D7 ]
+[D4,D5,D6,D7]
+
+>>> [ D5, D4 .. ]
+[D5,D4,D3,D2,D1,D0]
+
+>>> [ D1, D3 .. ]
+[D1,D3,D5,D7,D9]
+
+>>> [ minBound .. maxBound ] :: [D10]
+[D0,D1,D2,D3,D4,D5,D6,D7,D8,D9]
+
+-}
diff --git a/src/D10/Safe/Arithmetic.hs b/src/D10/Safe/Arithmetic.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe/Arithmetic.hs
@@ -0,0 +1,39 @@
+{-# language Safe #-}
+
+module D10.Safe.Arithmetic where
+
+import D10.Safe (D10, d10Int, intMod10)
+
+import qualified Prelude as P
+
+-- | Addition modulo 10.
+--
+-- >>> D2 + D3
+-- D5
+--
+-- >>> D6 + D7
+-- D3
+
+(+) :: D10 -> D10 -> D10
+x + y = intMod10 (d10Int x P.+ d10Int y)
+
+-- | Subtraction modulo 10.
+--
+-- >>> D7 - D5
+-- D2
+--
+-- >>> D3 - D7
+-- D6
+
+(-) :: D10 -> D10 -> D10
+x - y = intMod10 (d10Int x P.- d10Int y)
+
+-- | Multiplication modulo 10.
+--
+-- >>> D2 * D4
+-- D8
+-- >>> D7 * D8
+-- D6
+
+(*) :: D10 -> D10 -> D10
+x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/src/D10/Safe/Conversions.hs b/src/D10/Safe/Conversions.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe/Conversions.hs
@@ -0,0 +1,623 @@
+{-# language Safe #-}
+
+module D10.Safe.Conversions
+    (
+    -- * D10 / Char
+      d10Char, charD10Maybe, charD10Either, charD10Fail
+    -- * D10 / String
+    , d10Str, strD10Maybe, strD10Either, strD10Fail
+    -- * [D10] / String
+    , strD10ListMaybe, strD10ListEither, strD10ListFail
+    -- * D10 / Natural
+    , d10Nat, natD10Maybe, natD10Either, natD10Fail, natMod10
+    -- * D10 / Integer
+    , d10Integer, integerD10Maybe, integerD10Either
+    , integerD10Fail, integerMod10
+    -- * D10 / Int
+    , d10Int, intD10Maybe, intD10Either, intD10Fail, intMod10
+    -- * D10 / general numeric types
+    , d10Num, integralD10Maybe, integralD10Either
+    , integralD10Fail, integralMod10
+    ) where
+
+import D10.Safe.Type (D10 (..))
+
+-- base
+import Control.Monad.Fail (MonadFail (fail))
+import Numeric.Natural    (Natural)
+import Prelude            hiding (fail)
+
+-- | Convert a 'D10' to its underlying 'Char' representation.
+--
+-- >>> d10Char D7
+-- '7'
+
+d10Char :: D10 -> Char
+d10Char x =
+    case x of
+        D0 -> '0'
+        D1 -> '1'
+        D2 -> '2'
+        D3 -> '3'
+        D4 -> '4'
+        D5 -> '5'
+        D6 -> '6'
+        D7 -> '7'
+        D8 -> '8'
+        D9 -> '9'
+
+-- | Convert a 'D10' to a 'String'.
+--
+-- @'d10Str' x = ['d10Char' x]@
+--
+-- >>> d10Str D7
+-- "7"
+
+d10Str :: D10 -> String
+d10Str x = [d10Char x]
+
+-- | Convert a 'D10' to a 'Natural'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Nat D7
+-- 7
+
+d10Nat :: D10 -> Natural
+d10Nat = d10Num
+
+-- | Convert a 'D10' to an 'Integer'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Integer D7
+-- 7
+
+d10Integer :: D10 -> Integer
+d10Integer = d10Num
+
+-- | Convert a 'D10' to an 'Int'.
+--
+-- 'd10Num' is a more general version of this function.
+--
+-- >>> d10Int D7
+-- 7
+
+d10Int :: D10 -> Int
+d10Int = d10Num
+
+-- | Convert a 'D10' to any kind of number with a 'Num' instance.
+--
+-- Specialized versions of this function include 'd10Nat',
+-- 'd10Integer', and 'd10Int'.
+--
+-- >>> d10Num D7 :: Integer
+-- 7
+
+d10Num :: Num a => D10 -> a
+d10Num x =
+    case x of
+        D0 -> 0
+        D1 -> 1
+        D2 -> 2
+        D3 -> 3
+        D4 -> 4
+        D5 -> 5
+        D6 -> 6
+        D7 -> 7
+        D8 -> 8
+        D9 -> 9
+
+---------------------------------------------------
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> natMod10 56
+-- D6
+
+natMod10 :: Natural -> D10
+natMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> integerMod10 56
+-- D6
+--
+-- >>> integerMod10 (-56)
+-- D4
+
+integerMod10 :: Integer -> D10
+integerMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
+--
+-- 'integralMod10' is a more general version of this function.
+--
+-- >>> intMod10 56
+-- D6
+--
+-- >>> intMod10 (-56)
+-- D4
+
+intMod10 :: Int -> D10
+intMod10 = integralMod10
+
+-- | The 'D10' which is uniquely congruent modulo 10 to the given number
+-- (whose type must have an instance of the 'Integral' class).
+--
+-- Specialized versions of this function include 'natMod10',
+-- 'integerMod10', and 'intMod10'.
+--
+-- >>> integralMod10 (56 :: Integer)
+-- D6
+--
+-- >>> integralMod10 ((-56) :: Integer)
+-- D4
+
+integralMod10 :: Integral a => a -> D10
+integralMod10 x =
+    case (x `mod` 10) of
+        0 -> D0
+        1 -> D1
+        2 -> D2
+        3 -> D3
+        4 -> D4
+        5 -> D5
+        6 -> D6
+        7 -> D7
+        8 -> D8
+        9 -> D9
+        _ -> error "x `mod` 10 is not between 0 and 9"
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
+--
+-- 'charD10Fail' is a more general version of this function.
+--
+-- >>> charD10Maybe '5'
+-- Just D5
+--
+-- >>> charD10Maybe 'a'
+-- Nothing
+
+charD10Maybe :: Char -> Maybe D10
+charD10Maybe x =
+    case x of
+        '0' -> Just D0
+        '1' -> Just D1
+        '2' -> Just D2
+        '3' -> Just D3
+        '4' -> Just D4
+        '5' -> Just D5
+        '6' -> Just D6
+        '7' -> Just D7
+        '8' -> Just D8
+        '9' -> Just D9
+        _   -> Nothing
+
+-- | Convert a 'String' to a 'D10' if it consists of exactly one
+-- character and that character is within the range @'0'@ to @'9'@,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
+--
+-- 'strD10Fail' is a more general version of this function.
+--
+-- >>> strD10Maybe "5"
+-- Just D5
+--
+-- >>> strD10Maybe "a"
+-- Nothing
+--
+-- >>> strD10Maybe "58"
+-- Nothing
+
+strD10Maybe :: String -> Maybe D10
+strD10Maybe [x] = charD10Maybe x
+strD10Maybe _   = Nothing
+
+-- | Convert a 'String' to a list of 'D10' if all of the characters
+-- in the string are within the range @'0'@ to @'9'@, or produce
+-- 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
+--
+-- 'strD10ListFail' is a more general version of this function.
+--
+-- >>> strD10ListMaybe "5"
+-- Just [D5]
+--
+-- >>> strD10ListMaybe "a"
+-- Nothing
+--
+-- >>> strD10ListMaybe "58"
+-- Just [D5,D8]
+
+strD10ListMaybe :: String -> Maybe [D10]
+strD10ListMaybe = traverse charD10Maybe
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> natD10Maybe 5
+-- Just D5
+--
+-- >>> natD10Maybe 12
+-- Nothing
+
+natD10Maybe :: Natural -> Maybe D10
+natD10Maybe = integralD10Maybe
+
+-- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> integerD10Maybe 5
+-- Just D5
+--
+-- >>> integerD10Maybe 12
+-- Nothing
+--
+-- >>> integerD10Maybe (-5)
+-- Nothing
+
+integerD10Maybe :: Integer -> Maybe D10
+integerD10Maybe = integralD10Maybe
+
+-- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
+-- or produce 'Nothing' otherwise.
+--
+-- @'D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
+--
+-- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
+-- are more general versions of this function.
+--
+-- >>> intD10Maybe 5
+-- Just D5
+--
+-- >>> intD10Maybe 12
+-- Nothing
+--
+-- >>> intD10Maybe (-5)
+-- Nothing
+
+intD10Maybe :: Int -> Maybe D10
+intD10Maybe = integralD10Maybe
+
+-- | Construct a 'D10' from any kind of number with an 'Integral'
+-- instance, or produce 'Nothing' if the number falls outside the
+-- range 0 to 9.
+--
+-- @'D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
+--
+-- Specialized versions of this function include 'natD10Maybe',
+-- 'integerD10Maybe', and 'intD10Maybe'.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integralD10Maybe (5 :: Integer)
+-- Just D5
+--
+-- >>> integralD10Maybe (12 :: Integer)
+-- Nothing
+--
+-- >>> integralD10Maybe ((-5) :: Integer)
+-- Nothing
+
+integralD10Maybe :: Integral a => a -> Maybe D10
+integralD10Maybe x =
+    case x of
+        0 -> Just D0
+        1 -> Just D1
+        2 -> Just D2
+        3 -> Just D3
+        4 -> Just D4
+        5 -> Just D5
+        6 -> Just D6
+        7 -> Just D7
+        8 -> Just D8
+        9 -> Just D9
+        _ -> Nothing
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> charD10Either '5'
+-- Right D5
+--
+-- >>> charD10Either 'a'
+-- Left "d10 must be between 0 and 9"
+
+charD10Either :: Char -> Either String D10
+charD10Either x =
+    case x of
+        '0' -> Right D0
+        '1' -> Right D1
+        '2' -> Right D2
+        '3' -> Right D3
+        '4' -> Right D4
+        '5' -> Right D5
+        '6' -> Right D6
+        '7' -> Right D7
+        '8' -> Right D8
+        '9' -> Right D9
+        _   -> Left "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'Left' with an error message otherwise.
+--
+-- >>> strD10Either "5"
+-- Right D5
+--
+-- >>> strD10Either "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10Either "58"
+-- Left "d10 must be a single character"
+
+strD10Either :: String -> Either String D10
+strD10Either [x] = charD10Either x
+strD10Either _   = Left "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> strD10ListEither "5"
+-- Right [D5]
+--
+-- >>> strD10ListEither "a"
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> strD10ListEither "58"
+-- Right [D5,D8]
+
+strD10ListEither :: String -> Either String [D10]
+strD10ListEither = traverse charD10Either
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'Left' with an error message otherwise.
+--
+-- >>> natD10Either 5
+-- Right D5
+--
+-- >>> natD10Either 12
+-- Left "d10 must be less than 10"
+
+natD10Either :: Natural -> Either String D10
+natD10Either x =
+    case x of
+        0 -> Right D0
+        1 -> Right D1
+        2 -> Right D2
+        3 -> Right D3
+        4 -> Right D4
+        5 -> Right D5
+        6 -> Right D6
+        7 -> Right D7
+        8 -> Right D8
+        9 -> Right D9
+        _ -> Left "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> integerD10Either 5
+-- Right D5
+--
+-- >>> integerD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integerD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+integerD10Either :: Integer -> Either String D10
+integerD10Either = integralD10Either
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'Left' with an error message otherwise.
+--
+-- >>> intD10Either 5
+-- Right D5
+--
+-- >>> intD10Either 12
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> intD10Either (-5)
+-- Left "d10 must be between 0 and 9"
+
+intD10Either :: Int -> Either String D10
+intD10Either = integralD10Either
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'Left'
+-- with an error message otherwise.
+--
+-- >>> integralD10Either (5 :: Integer)
+-- Right D5
+--
+-- >>> integralD10Either (12 :: Integer)
+-- Left "d10 must be between 0 and 9"
+--
+-- >>> integralD10Either ((-5) :: Integer)
+-- Left "d10 must be between 0 and 9"
+
+integralD10Either :: Integral a => a -> Either String D10
+integralD10Either x =
+    case (integralD10Maybe x) of
+        Just y  -> Right y
+        Nothing -> Left "d10 must be between 0 and 9"
+
+---------------------------------------------------
+
+-- | Convert a 'Char' to a 'D10' if it is within the range
+-- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'charD10Maybe' is a specialized version of this function.
+--
+-- >>> charD10Fail '5' :: IO D10
+-- D5
+--
+-- >>> charD10Fail 'a' :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+charD10Fail :: MonadFail m => Char -> m D10
+charD10Fail x =
+    case x of
+        '0' -> return D0
+        '1' -> return D1
+        '2' -> return D2
+        '3' -> return D3
+        '4' -> return D4
+        '5' -> return D5
+        '6' -> return D6
+        '7' -> return D7
+        '8' -> return D8
+        '9' -> return D9
+        _   -> fail "d10 must be between 0 and 9"
+
+-- | Convert a 'String' to a 'D10' if it consists of a single
+-- character and that character is within the range @'0'@ to
+-- @'9'@, or 'fail' with an error message otherwise.
+--
+-- 'strD10Maybe' is a specialized version of this function.
+--
+-- >>> strD10Fail "5" :: IO D10
+-- D5
+--
+-- >>> strD10Fail "a" :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10Fail "58" :: IO D10
+-- *** Exception: user error (d10 must be a single character)
+
+strD10Fail :: MonadFail m => String -> m D10
+strD10Fail [x] = charD10Fail x
+strD10Fail _   = fail "d10 must be a single character"
+
+-- | Convert a 'String' to a 'D10' if all of the characters in
+-- the string fall within the range @'0'@ to @'9'@, or 'fail'
+-- with an error message otherwise.
+--
+-- 'strD10ListMaybe' is a specialized version of this function.
+--
+-- >>> strD10ListFail "5" :: IO [D10]
+-- [D5]
+--
+-- >>> strD10ListFail "a" :: IO [D10]
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> strD10ListFail "58" :: IO [D10]
+-- [D5,D8]
+
+strD10ListFail :: MonadFail m => String -> m [D10]
+strD10ListFail = traverse charD10Fail
+
+-- | Convert a 'Natural' to a 'D10' if it is less than 10,
+-- or 'fail' with an error message otherwise.
+--
+-- 'natD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> natD10Fail 5 :: IO D10
+-- D5
+--
+-- >>> natD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be less than 10)
+
+natD10Fail :: MonadFail m => Natural -> m D10
+natD10Fail x =
+    case x of
+        0 -> return D0
+        1 -> return D1
+        2 -> return D2
+        3 -> return D3
+        4 -> return D4
+        5 -> return D5
+        6 -> return D6
+        7 -> return D7
+        8 -> return D8
+        9 -> return D9
+        _ -> fail "d10 must be less than 10"
+
+-- | Convert an 'Integer' to a 'D10' if it is within the
+-- range 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'integerD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> integerD10Fail 5 :: IO D10
+-- D5
+--
+-- >>> integerD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integerD10Fail (-5) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integerD10Fail :: MonadFail m => Integer -> m D10
+integerD10Fail = integralD10Fail
+
+-- | Convert an 'Int' to a 'D10' if it is within the range
+-- 0 to 9, or 'fail' with an error message otherwise.
+--
+-- 'intD10Maybe' is a specialized version of this function.
+--
+-- 'integralD10Fail' is a more general version of this function.
+--
+-- >>> intD10Fail 5 :: IO D10
+-- D5
+--
+-- >>> intD10Fail 12 :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> intD10Fail (-5) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+intD10Fail :: MonadFail m => Int -> m D10
+intD10Fail = integralD10Fail
+
+-- | Convert a number of a type that has an 'Integral' instance
+-- to a 'D10' if it falls within the range 0 to 9, or 'fail'
+-- with an error message otherwise.
+--
+-- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
+-- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
+-- 'intD10Fail' are all specialized versions of this function.
+--
+-- >>> integralD10Fail (5 :: Integer) :: IO D10
+-- D5
+--
+-- >>> integralD10Fail (12 :: Integer) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+--
+-- >>> integralD10Fail ((-5) :: Integer) :: IO D10
+-- *** Exception: user error (d10 must be between 0 and 9)
+
+integralD10Fail :: (Integral a, MonadFail m) => a -> m D10
+integralD10Fail x =
+    case (integralD10Maybe x) of
+        Just y  -> return y
+        Nothing -> fail "d10 must be between 0 and 9"
diff --git a/src/D10/Safe/Quotes.hs b/src/D10/Safe/Quotes.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe/Quotes.hs
@@ -0,0 +1,56 @@
+{-# language Safe #-}
+
+module D10.Safe.Quotes (d10list) where
+
+import D10.Safe.Splices
+
+import Control.Monad.Fail (MonadFail (fail))
+import Language.Haskell.TH.Quote (QuasiQuoter (..))
+import Prelude hiding (fail)
+
+-- | A list of base-10 digits.
+--
+-- This quasi-quoter, when used as an expression, produces a
+-- value of type @['D10']@.
+--
+-- >>> [d10list||]
+-- []
+--
+-- >>> [d10list|5|]
+-- [D5]
+--
+-- >>> [d10list|58|]
+-- [D5,D8]
+--
+-- >>> [d10list|a|]
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- This quasi-quoter can also be used as a pattern.
+--
+-- >>> :{
+--       case [D5, D6] of
+--         [d10list|41|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- "B"
+--
+-- >>> :{
+--       case [D5, D6] of
+--         [d10list|4x|] -> "A"
+--         [d10list|56|] -> "B"
+--         _             -> "C"
+-- >>> :}
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+
+d10list :: QuasiQuoter
+d10list = QuasiQuoter
+    { quoteExp  = d10ListExp
+    , quotePat  = d10ListPat
+    , quoteType = \_ -> fail "d10list cannot be used in a type context"
+    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
+    }
diff --git a/src/D10/Safe/Splices.hs b/src/D10/Safe/Splices.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe/Splices.hs
@@ -0,0 +1,59 @@
+{-# language Safe #-}
+
+module D10.Safe.Splices
+    (
+    -- * Splice expressions
+      d10ListExp
+    -- * Splice patterns
+    , d10ListPat
+    ) where
+
+import D10.Safe.Conversions (strD10ListFail)
+
+import Control.Monad ((>=>))
+import Language.Haskell.TH.Syntax (Exp (..), Pat (..), Q, dataToExpQ, dataToPatQ)
+
+-- | Produces an expression of type @['D10']@ that can be used
+-- in a Template Haskell splice.
+--
+-- >>> $(d10ListExp "")
+-- []
+--
+-- >>> $(d10ListExp "5")
+-- [D5]
+--
+-- >>> $(d10ListExp "58")
+-- [D5,D8]
+--
+-- >>> $(d10ListExp "a")
+-- ...
+-- ... d10 must be between 0 and 9
+-- ...
+--
+-- You may also be interested in 'D10.Safe.Quotes.d10list',
+-- a quasi-quoter which does something similar.
+
+d10ListExp :: String -> Q Exp
+d10ListExp = strD10ListFail >=> dataToExpQ (const Nothing)
+
+---------------------------------------------------
+
+-- | Produces a pattern that can be used in a splice
+-- to match a particular list of 'D10' values.
+--
+-- >>> :{
+--       case [D5, D6] of
+--         $(d10ListPat "42") -> "A"
+--         $(d10ListPat "56") -> "B"
+--         _                  -> "C"
+-- >>> :}
+-- "B"
+--
+-- You may also be interested in 'D10.Safe.Quotes.d10list',
+-- a quasi-quoter which does something similar.
+
+d10ListPat :: String -> Q Pat
+d10ListPat = strD10ListFail >=> \xs ->
+  do
+    pats <- traverse (dataToPatQ (const Nothing)) xs
+    return (ListP pats)
diff --git a/src/D10/Safe/Type.hs b/src/D10/Safe/Type.hs
new file mode 100644
--- /dev/null
+++ b/src/D10/Safe/Type.hs
@@ -0,0 +1,20 @@
+{-# language Safe #-}
+
+module D10.Safe.Type (D10 (..)) where
+
+import Data.Data (Data)
+import GHC.Generics (Generic)
+
+-- | A whole number between /0/ and /9/
+data D10
+    = D0  -- ^ Zero
+    | D1  -- ^ One
+    | D2  -- ^ Two
+    | D3  -- ^ Three
+    | D4  -- ^ Four
+    | D5  -- ^ Five
+    | D6  -- ^ Six
+    | D7  -- ^ Seven
+    | D8  -- ^ Eight
+    | D9  -- ^ Nine
+    deriving (Bounded, Enum, Eq, Ord, Show, Data, Generic)
diff --git a/src/Data/D10/Char.hs b/src/Data/D10/Char.hs
deleted file mode 100644
--- a/src/Data/D10/Char.hs
+++ /dev/null
@@ -1,917 +0,0 @@
--- | Defines a 'D10' type as a newtype for 'Char', where the
--- values are restricted to characters between @'0'@ and @'9'@.
---
--- The following modules define @D10@ types in different ways
--- but are otherwise very similar to this one:
---
--- * "Data.D10.Num"
--- * "Data.D10.Safe"
-
-module Data.D10.Char
-    (
-    -- * Type
-      D10 (..)
-    -- $bounded
-    -- $enum
-
-    -- * Quasi-quoters
-    , d10
-    , d10list
-
-    -- * Splice expressions
-    , d10Exp
-    , d10ListExp
-
-    -- * Splice patterns
-    , d10Pat
-    , d10ListPat
-
-    -- * Converting between D10 and Char
-    , d10Char
-    , charD10Maybe
-    , charD10Either
-    , charD10Fail
-
-    -- * Converting between D10 and String
-    , d10Str
-    , strD10Maybe
-    , strD10Either
-    , strD10Fail
-
-    -- * Converting between [D10] and String
-    , strD10ListMaybe
-    , strD10ListEither
-    , strD10ListFail
-
-    -- * Converting between D10 and Natural
-    , d10Nat
-    , natD10Maybe
-    , natD10Either
-    , natD10Fail
-    , natMod10
-
-    -- * Converting between D10 and Integer
-    , d10Integer
-    , integerD10Maybe
-    , integerD10Either
-    , integerD10Fail
-    , integerMod10
-
-    -- * Converting between D10 and Int
-    , d10Int
-    , intD10Maybe
-    , intD10Either
-    , intD10Fail
-    , intMod10
-
-    -- * Converting between D10 and general numeric types
-    , d10Num
-    , integralD10Maybe
-    , integralD10Either
-    , integralD10Fail
-    , integralMod10
-
-    -- * Modular arithmetic
-    , (+), (-), (*)
-
-    ) where
-
-import Data.D10.Predicate
-
--- base
-import Control.Monad      ((>=>))
-import Control.Monad.Fail (MonadFail (fail))
-import Data.Char          (chr, ord)
-import Data.Monoid        (Endo (..))
-import Numeric.Natural    (Natural)
-import Prelude            hiding (fail, (+), (-), (*))
-
-import qualified Prelude as P
-
--- template-haskell
-import Language.Haskell.TH.Lib    (litP, charL)
-import Language.Haskell.TH.Quote  (QuasiQuoter (..))
-import Language.Haskell.TH.Syntax (Exp (..), Lift, Pat (..), Q)
-
----------------------------------------------------
-
--- | A 'Char' value between @'0'@ and @'9'@.
---
--- The "Data.D10.Char" module provides many functions for
--- constructing 'D10' values, including:
---
--- * @'integerD10Maybe' :: 'Integer' -> 'Maybe' 'D10'@
--- * @'integerMod10' :: 'Integer' -> 'D10'@
---
--- With the @QuasiQuotes@ GHC extension enabled, you can write
--- 'D10' literals using the quasi-quoters 'd10' and 'd10list'.
-
-newtype D10 =
-    D10_Unsafe Char
-      -- ^ The constructor's name include the word "unsafe" as a reminder
-      --   that you should generally avoid using it directly, because it
-      --   allows constructing invalid 'D10' values.
-    deriving (Eq, Ord, Lift)
-
--- $bounded
--- ==== Bounded
---
--- >>> minBound :: D10
--- [d10|0|]
---
--- >>> maxBound :: D10
--- [d10|9|]
-
-instance Bounded D10
-  where
-    minBound = D10_Unsafe '0'
-    maxBound = D10_Unsafe '9'
-
--- $enum
--- ==== Enum
---
--- >>> [ [d10|5|] .. ]
--- [d10list|56789|]
---
--- >>> [ [d10|4|] .. [d10|7|] ]
--- [d10list|4567|]
---
--- >>> [ [d10|5|], [d10|4|] .. ]
--- [d10list|543210|]
---
--- >>> [ [d10|1|], [d10|3|] .. ]
--- [d10list|13579|]
---
--- >>> [ minBound .. maxBound ] :: [D10]
--- [d10list|0123456789|]
-
-instance Enum D10
-  where
-    fromEnum :: D10 -> Int
-    fromEnum = d10Int
-
-    toEnum :: Int -> D10
-    toEnum = either error id . intD10Either
-
-    enumFrom :: D10 -> [D10]
-    enumFrom x = enumFromTo x maxBound
-
-    enumFromThen :: D10 -> D10 -> [D10]
-    enumFromThen x y = enumFromThenTo x y bound
-      where
-        bound | fromEnum y >= fromEnum x = maxBound
-              | otherwise                = minBound
-
-    succ :: D10 -> D10
-    succ (D10_Unsafe '9') = error "D10 overflow"
-    succ (D10_Unsafe x) = D10_Unsafe (succ x)
-
-    pred :: D10 -> D10
-    pred (D10_Unsafe '0') = error "D10 underflow"
-    pred (D10_Unsafe x) = D10_Unsafe (pred x)
-
----------------------------------------------------
-
--- | Shows base-10 digits using the quasiquoters defined in
--- "Data.D10.Char". A single digit is displayed using 'd10'.
--- A list of digits is displayed using 'd10list'.
-
-instance Show D10 where
-    showsPrec _ x = showString "[d10|"     . showsChar x . showString "|]"
-    showList xs   = showString "[d10list|" . showsStr xs . showString "|]"
-
-showsChar :: D10 -> ShowS
-showsChar (D10_Unsafe x) = showChar x
-
-showsStr :: [D10] -> ShowS
-showsStr = appEndo . foldMap (Endo . showsChar)
-
----------------------------------------------------
-
--- | Convert a 'D10' to its underlying 'Char' representation.
---
--- >>> d10Char [d10|7|]
--- '7'
-
-d10Char :: D10 -> Char
-d10Char (D10_Unsafe x) = x
-
--- | Convert a 'D10' to a 'String'.
---
--- @'d10Str' x = ['d10Char' x]@
---
--- >>> d10Str [d10|7|]
--- "7"
-
-d10Str :: D10 -> String
-d10Str (D10_Unsafe x) = [x]
-
--- | Convert a 'D10' to a 'Natural'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Nat [d10|7|]
--- 7
-
-d10Nat :: D10 -> Natural
-d10Nat (D10_Unsafe x) = fromIntegral (ord x P.- ord '0')
-
--- | Convert a 'D10' to an 'Integer'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Integer [d10|7|]
--- 7
-
-d10Integer :: D10 -> Integer
-d10Integer (D10_Unsafe x) = toInteger (ord x P.- ord '0')
-
--- | Convert a 'D10' to an 'Int'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Int [d10|7|]
--- 7
-
-d10Int :: D10 -> Int
-d10Int (D10_Unsafe x) = ord x P.- ord '0'
-
--- | Convert a 'D10' to any kind of number with a 'Num' instance.
---
--- Specialized versions of this function include 'd10Nat',
--- 'd10Integer', and 'd10Int'.
---
--- >>> d10Num [d10|7|] :: Integer
--- 7
-
-d10Num :: Num a => D10 -> a
-d10Num (D10_Unsafe x) = fromIntegral (ord x P.- ord '0')
-
----------------------------------------------------
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> natMod10 56
--- [d10|6|]
-
-natMod10 :: Natural -> D10
-natMod10 x = D10_Unsafe (chr (ord '0' P.+ fromIntegral (x `mod` 10)))
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> integerMod10 56
--- [d10|6|]
---
--- >>> integerMod10 (-56)
--- [d10|4|]
-
-integerMod10 :: Integer -> D10
-integerMod10 x = D10_Unsafe (chr (ord '0' P.+ fromInteger (x `mod` 10)))
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> intMod10 56
--- [d10|6|]
---
--- >>> intMod10 (-56)
--- [d10|4|]
-
-intMod10 :: Int -> D10
-intMod10 x = D10_Unsafe (chr (ord '0' P.+ (x `mod` 10)))
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given number
--- (whose type must have an instance of the 'Integral' class).
---
--- Specialized versions of this function include 'natMod10',
--- 'integerMod10', and 'intMod10'.
---
--- >>> integralMod10 (56 :: Integer)
--- [d10|6|]
---
--- >>> integralMod10 ((-56) :: Integer)
--- [d10|4|]
-
-integralMod10 :: Integral a => a -> D10
-integralMod10 x = D10_Unsafe (chr (ord '0' P.+ fromIntegral (x `mod` 10)))
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
---
--- 'charD10Fail' is a more general version of this function.
---
--- >>> charD10Maybe '5'
--- Just [d10|5|]
---
--- >>> charD10Maybe 'a'
--- Nothing
-
-charD10Maybe :: Char -> Maybe D10
-charD10Maybe x
-        | isD10Char x  =  Just (D10_Unsafe x)
-        | otherwise    =  Nothing
-
--- | Convert a 'String' to a 'D10' if it consists of exactly one
--- character and that character is within the range @'0'@ to @'9'@,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
---
--- 'strD10Fail' is a more general version of this function.
---
--- >>> strD10Maybe "5"
--- Just [d10|5|]
---
--- >>> strD10Maybe "a"
--- Nothing
---
--- >>> strD10Maybe "58"
--- Nothing
-
-strD10Maybe :: String -> Maybe D10
-strD10Maybe [x] = charD10Maybe x
-strD10Maybe _   = Nothing
-
--- | Convert a 'String' to a list of 'D10' if all of the characters
--- in the string are within the range @'0'@ to @'9'@, or produce
--- 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
---
--- 'strD10ListFail' is a more general version of this function.
---
--- >>> strD10ListMaybe "5"
--- Just [d10list|5|]
---
--- >>> strD10ListMaybe "a"
--- Nothing
---
--- >>> strD10ListMaybe "58"
--- Just [d10list|58|]
-
-strD10ListMaybe :: String -> Maybe [D10]
-strD10ListMaybe = traverse charD10Maybe
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
---
--- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> natD10Maybe 5
--- Just [d10|5|]
---
--- >>> natD10Maybe 12
--- Nothing
-
-natD10Maybe :: Natural -> Maybe D10
-natD10Maybe x
-        | isD10Nat x  =  Just (D10_Unsafe (chr (fromIntegral x P.+ ord '0')))
-        | otherwise   =  Nothing
-
--- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
---
--- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> integerD10Maybe 5
--- Just [d10|5|]
---
--- >>> integerD10Maybe 12
--- Nothing
---
--- >>> integerD10Maybe (-5)
--- Nothing
-
-integerD10Maybe :: Integer -> Maybe D10
-integerD10Maybe x
-        | isD10Integer x  =  Just (D10_Unsafe (chr (fromInteger x P.+ ord '0')))
-        | otherwise       =  Nothing
-
--- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
---
--- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> intD10Maybe 5
--- Just [d10|5|]
---
--- >>> intD10Maybe 12
--- Nothing
---
--- >>> intD10Maybe (-5)
--- Nothing
-
-intD10Maybe :: Int -> Maybe D10
-intD10Maybe x
-        | isD10Int x  =  Just (D10_Unsafe (chr (x P.+ ord '0')))
-        | otherwise   =  Nothing
-
--- | Construct a 'D10' from any kind of number with an 'Integral'
--- instance, or produce 'Nothing' if the number falls outside the
--- range 0 to 9.
---
--- @'Data.D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
---
--- Specialized versions of this function include 'natD10Maybe',
--- 'integerD10Maybe', and 'intD10Maybe'.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integralD10Maybe (5 :: Integer)
--- Just [d10|5|]
---
--- >>> integralD10Maybe (12 :: Integer)
--- Nothing
---
--- >>> integralD10Maybe ((-5) :: Integer)
--- Nothing
-
-integralD10Maybe :: Integral a => a -> Maybe D10
-integralD10Maybe x = integerD10Maybe (toInteger x)
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
---
--- >>> charD10Either '5'
--- Right [d10|5|]
---
--- >>> charD10Either 'a'
--- Left "d10 must be between 0 and 9"
-
-charD10Either :: Char -> Either String D10
-charD10Either x
-        | isD10Char x  =  Right (D10_Unsafe x)
-        | otherwise    =  Left "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'Left' with an error message otherwise.
---
--- >>> strD10Either "5"
--- Right [d10|5|]
---
--- >>> strD10Either "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10Either "58"
--- Left "d10 must be a single character"
-
-strD10Either :: String -> Either String D10
-strD10Either [x]         =  charD10Either x
-strD10Either _           =  Left "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'Left'
--- with an error message otherwise.
---
--- >>> strD10ListEither "5"
--- Right [d10list|5|]
---
--- >>> strD10ListEither "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10ListEither "58"
--- Right [d10list|58|]
-
-strD10ListEither :: String -> Either String [D10]
-strD10ListEither = traverse charD10Either
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'Left' with an error message otherwise.
---
--- >>> natD10Either 5
--- Right [d10|5|]
---
--- >>> natD10Either 12
--- Left "d10 must be less than 10"
-
-natD10Either :: Natural -> Either String D10
-natD10Either x =
-    case (natD10Maybe x) of
-        Just y  -> Right y
-        Nothing -> Left "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> integerD10Either 5
--- Right [d10|5|]
---
--- >>> integerD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> integerD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-integerD10Either :: Integer -> Either String D10
-integerD10Either x =
-    case (integerD10Maybe x) of
-        Just y  -> Right y
-        Nothing -> Left "d10 must be between 0 and 9"
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> intD10Either 5
--- Right [d10|5|]
---
--- >>> intD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> intD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-intD10Either :: Int -> Either String D10
-intD10Either x =
-    case (intD10Maybe x) of
-        Just y  ->  Right y
-        Nothing ->  Left "d10 must be between 0 and 9"
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'Left'
--- with an error message otherwise.
---
--- >>> integralD10Either (5 :: Integer)
--- Right [d10|5|]
---
--- >>> integralD10Either (12 :: Integer)
--- Left "d10 must be between 0 and 9"
---
--- >>> integralD10Either ((-5) :: Integer)
--- Left "d10 must be between 0 and 9"
-
-integralD10Either :: Integral a => a -> Either String D10
-integralD10Either x = integerD10Either (toInteger x)
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
---
--- 'charD10Maybe' is a specialized version of this function.
---
--- >>> charD10Fail '5' :: IO D10
--- [d10|5|]
---
--- >>> charD10Fail 'a' :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-charD10Fail :: MonadFail m => Char -> m D10
-charD10Fail x
-        | isD10Char x  =  return (D10_Unsafe x)
-        | otherwise    =  fail "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'fail' with an error message otherwise.
---
--- 'strD10Maybe' is a specialized version of this function.
---
--- >>> strD10Fail "5" :: IO D10
--- [d10|5|]
---
--- >>> strD10Fail "a" :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10Fail "58" :: IO D10
--- *** Exception: user error (d10 must be a single character)
-
-strD10Fail :: MonadFail m => String -> m D10
-strD10Fail [x]         =  charD10Fail x
-strD10Fail _           =  fail "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'fail'
--- with an error message otherwise.
---
--- 'strD10ListMaybe' is a specialized version of this function.
---
--- >>> strD10ListFail "5" :: IO [D10]
--- [d10list|5|]
---
--- >>> strD10ListFail "a" :: IO [D10]
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10ListFail "58" :: IO [D10]
--- [d10list|58|]
-
-strD10ListFail :: MonadFail m => String -> m [D10]
-strD10ListFail = traverse charD10Fail
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'fail' with an error message otherwise.
---
--- 'natD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> natD10Fail 5 :: IO D10
--- [d10|5|]
---
--- >>> natD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be less than 10)
-
-natD10Fail :: MonadFail m => Natural -> m D10
-natD10Fail x =
-    case (natD10Maybe x) of
-        Just y  -> return y
-        Nothing -> fail "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'fail' with an error message otherwise.
---
--- 'integerD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integerD10Fail 5 :: IO D10
--- [d10|5|]
---
--- >>> integerD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integerD10Fail (-5) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integerD10Fail :: MonadFail m => Integer -> m D10
-integerD10Fail x =
-    case (integerD10Maybe x) of
-        Just y  -> return y
-        Nothing -> fail "d10 must be between 0 and 9"
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'fail' with an error message otherwise.
---
--- 'intD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> intD10Fail 5 :: IO D10
--- [d10|5|]
---
--- >>> intD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> intD10Fail (-5) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-intD10Fail :: MonadFail m => Int -> m D10
-intD10Fail x =
-    case (intD10Maybe x) of
-        Just y  ->  return y
-        Nothing ->  fail "d10 must be between 0 and 9"
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'fail'
--- with an error message otherwise.
---
--- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
--- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
--- 'intD10Fail' are all specialized versions of this function.
---
--- >>> integralD10Fail (5 :: Integer) :: IO D10
--- [d10|5|]
---
--- >>> integralD10Fail (12 :: Integer) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integralD10Fail ((-5) :: Integer) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integralD10Fail :: (Integral a, MonadFail m) => a -> m D10
-integralD10Fail x = integerD10Fail (toInteger x)
-
----------------------------------------------------
-
--- | Produces an expression of type 'D10' that can be used
--- in a Template Haskell splice.
---
--- >>> d10Nat $(d10Exp 5)
--- 5
---
--- >>> d10Nat $(d10Exp 12)
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- You may also be interested in 'd10', a quasi-quoter which
--- does something similar.
-
-d10Exp :: Integer -> Q Exp
-d10Exp = integerD10Fail >=> d10Exp'
-
-d10Exp' :: D10 -> Q Exp
-d10Exp' x = [| x |]
-
--- | Produces an expression of type @['D10']@ that can be used
--- in a Template Haskell splice.
---
--- >>> d10Nat <$> $(d10ListExp "")
--- []
---
--- >>> d10Nat <$> $(d10ListExp "5")
--- [5]
---
--- >>> d10Nat <$> $(d10ListExp "58")
--- [5,8]
---
--- >>> d10Nat <$> $(d10ListExp "a")
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- You may also be interested in 'd10list', a quasi-quoter which
--- does something similar.
-
-d10ListExp :: String -> Q Exp
-d10ListExp = strD10ListFail >=> d10ListExp'
-
-d10ListExp' :: [D10] -> Q Exp
-d10ListExp' xs = [| xs |]
-
----------------------------------------------------
-
--- | Produces a pattern that can be used in a splice
--- to match a particular 'D10' value.
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just $(d10Pat 4) -> "A"
---         Just $(d10Pat 5) -> "B"
---         _                -> "C"
--- >>> :}
--- "B"
---
--- You may wish to use the 'd10' quasi-quoter instead.
-
-d10Pat :: Integer -> Q Pat
-d10Pat = integerD10Fail >=> d10Pat'
-
-d10Pat' :: D10 -> Q Pat
-d10Pat' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ charL x) |]
-
--- | Produces a pattern that can be used in a splice
--- to match a particular list of 'D10' values.
---
--- >>> :{
---       case (strD10ListMaybe "56") of
---         Just $(d10ListPat "42") -> "A"
---         Just $(d10ListPat "56") -> "B"
---         _                       -> "C"
--- >>> :}
--- "B"
---
--- You may wish to use the 'd10list' quasi-quoter instead.
-
-d10ListPat :: String -> Q Pat
-d10ListPat = strD10ListFail >=> foldr (\x p -> [p| $(d10Pat' x) : $(p) |]) [p| [] |]
-
---------------------------------------------------
-
--- | A single base-10 digit.
---
--- This quasi-quoter, when used as an expression, produces a
--- value of type 'D10'.
---
--- >>> d10Nat [d10|5|]
--- 5
---
--- >>> d10Nat [d10|a|]
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- >>> d10Nat [d10|58|]
--- ...
--- ... d10 must be a single character
--- ...
---
--- This quasi-quoter can also be used as a pattern.
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just [d10|4|] -> "A"
---         Just [d10|5|] -> "B"
---         _             -> "C"
--- >>> :}
--- "B"
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just [d10|x|] -> "A"
---         Just [d10|5|] -> "B"
---         _             -> "C"
--- >>> :}
--- ...
--- ... d10 must be between 0 and 9
--- ...
-
-d10 :: QuasiQuoter
-d10 = QuasiQuoter
-    { quoteExp  = strD10Fail >=> d10Exp'
-    , quotePat  = strD10Fail >=> d10Pat'
-    , quoteType = \_ -> fail "d10 cannot be used in a type context"
-    , quoteDec  = \_ -> fail "d10 cannot be used in a declaration context"
-    }
-
--- | A list of base-10 digits.
---
--- This quasi-quoter, when used as an expression, produces a
--- value of type @['D10']@.
---
--- >>> d10Nat <$> [d10list||]
--- []
---
--- >>> d10Nat <$> [d10list|5|]
--- [5]
---
--- >>> d10Nat <$> [d10list|58|]
--- [5,8]
---
--- >>> d10Nat <$> [d10list|a|]
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- This quasi-quoter can also be used as a pattern.
---
--- >>> :{
---       case [d10list|56|] of
---         [d10list|41|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- "B"
---
--- >>> :{
---       case [d10list|56|] of
---         [d10list|4x|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- ...
--- ... d10 must be between 0 and 9
--- ...
-
-d10list :: QuasiQuoter
-d10list = QuasiQuoter
-    { quoteExp  = strD10ListFail >=> d10ListExp'
-    , quotePat  = d10ListPat
-    , quoteType = \_ -> fail "d10list cannot be used in a type context"
-    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
-    }
-
----------------------------------------------------
-
--- | Addition modulo 10.
---
--- >>> [d10|2|] + [d10|3|]
--- [d10|5|]
---
--- >>> [d10|6|] + [d10|7|]
--- [d10|3|]
-
-(+) :: D10 -> D10 -> D10
-x + y = intMod10 (d10Int x P.+ d10Int y)
-
--- | Subtraction modulo 10.
---
--- >>> [d10|7|] - [d10|5|]
--- [d10|2|]
---
--- >>> [d10|3|] - [d10|7|]
--- [d10|6|]
-
-(-) :: D10 -> D10 -> D10
-x - y = intMod10 (d10Int x P.- d10Int y)
-
--- | Multiplication modulo 10.
---
--- >>> [d10|2|] * [d10|4|]
--- [d10|8|]
---
--- >>> [d10|7|] * [d10|8|]
--- [d10|6|]
-
-(*) :: D10 -> D10 -> D10
-x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/src/Data/D10/Num.hs b/src/Data/D10/Num.hs
deleted file mode 100644
--- a/src/Data/D10/Num.hs
+++ /dev/null
@@ -1,928 +0,0 @@
--- | Defines a 'D10' type as a newtype for any type with an
--- instance of the 'Num' class, where the values are restricted
--- to numbers between @'fromInteger' 0@ and @'fromInteger' 9@.
---
--- The following modules define @D10@ types in different ways
--- but are otherwise very similar to this one:
---
--- * "Data.D10.Char"
--- * "Data.D10.Safe"
-
-module Data.D10.Num
-    (
-    -- * Type
-      D10 (..)
-    -- $bounded
-    -- $enum
-
-    -- * Quasi-quoters
-    , d10
-    , d10list
-
-    -- * Splice expressions
-    , d10Exp
-    , d10ListExp
-
-    -- * Splice patterns
-    , d10Pat
-    , d10ListPat
-
-    -- * Converting between D10 and Char
-    , d10Char
-    , charD10Maybe
-    , charD10Either
-    , charD10Fail
-
-    -- * Converting between D10 and String
-    , d10Str
-    , strD10Maybe
-    , strD10Either
-    , strD10Fail
-
-    -- * Converting between [D10] and String
-    , strD10ListMaybe
-    , strD10ListEither
-    , strD10ListFail
-
-    -- * Converting between D10 and Natural
-    , d10Nat
-    , natD10Maybe
-    , natD10Either
-    , natD10Fail
-    , natMod10
-
-    -- * Converting between D10 and Integer
-    , d10Integer
-    , integerD10Maybe
-    , integerD10Either
-    , integerD10Fail
-    , integerMod10
-
-    -- * Converting between D10 and Int
-    , d10Int
-    , intD10Maybe
-    , intD10Either
-    , intD10Fail
-    , intMod10
-
-    -- * Converting between D10 and general numeric types
-    , d10Num
-    , integralD10Maybe
-    , integralD10Either
-    , integralD10Fail
-    , integralMod10
-
-    -- * Modular arithmetic
-    , (+), (-), (*)
-
-    ) where
-
-import Data.D10.Predicate
-
--- base
-import Control.Monad      ((>=>))
-import Control.Monad.Fail (MonadFail (fail))
-import Data.Char          (chr, ord)
-import Data.Monoid        (Endo (..))
-import Numeric.Natural    (Natural)
-import Prelude            hiding (fail, (+), (-), (*))
-
-import qualified Prelude as P
-
--- template-haskell
-import Language.Haskell.TH.Lib    (litP, integerL)
-import Language.Haskell.TH.Quote  (QuasiQuoter (..))
-import Language.Haskell.TH.Syntax (Exp (..), Lift, Pat (..), Q)
-
----------------------------------------------------
-
--- | A value of some numeric type @a@ between
--- @'fromInteger' 0@ and @'fromInteger' 9@.
---
--- The "Data.D10.Num" module provides many functions for
--- constructing 'D10' values, including:
---
--- * @'integerD10Maybe' :: 'Integer' -> 'Maybe' 'D10'@
--- * @'integerMod10' :: 'Integer' -> 'D10'@
---
--- There are also several ways to safely write 'D10' literals
--- using Template Haskell:
---
--- * With the @QuasiQuotes@ GHC extension enabled, you can write
---   use the quasi-quoters 'd10' and 'd10list'.
--- * With the @TemplateHaskell@ GHC extension enabled, you can
---   splice expressions produced by 'd10Exp' and 'd10ListExp'.
-
-newtype D10 a =
-    D10_Unsafe a
-      -- ^ The constructor's name include the word "unsafe" as a reminder
-      --   that you should generally avoid using it directly, because it
-      --   allows constructing invalid 'D10' values.
-    deriving (Eq, Ord, Lift)
-
--- $bounded
--- ==== Bounded
---
--- >>> minBound :: D10 Integer
--- [d10|0|]
---
--- >>> maxBound :: D10 Integer
--- [d10|9|]
-
-instance Num a => Bounded (D10 a)
-  where
-    minBound = D10_Unsafe 0
-    maxBound = D10_Unsafe 9
-
--- $enum
--- ==== Enum
---
--- >>> [ [d10|5|] .. ]
--- [d10list|56789|]
---
--- >>> [ [d10|4|] .. [d10|7|] ]
--- [d10list|4567|]
---
--- >>> [ [d10|5|], [d10|4|] .. ]
--- [d10list|543210|]
---
--- >>> [ [d10|1|], [d10|3|] .. ]
--- [d10list|13579|]
---
--- >>> [ minBound .. maxBound ] :: [D10 Integer]
--- [d10list|0123456789|]
-
-instance Integral a => Enum (D10 a)
-  where
-    fromEnum :: D10 a -> Int
-    fromEnum = d10Int
-
-    toEnum :: Int -> D10 a
-    toEnum = either error id . intD10Either
-
-    enumFrom :: D10 a -> [D10 a]
-    enumFrom x = enumFromTo x maxBound
-
-    enumFromThen :: D10 a -> D10 a -> [D10 a]
-    enumFromThen x y = enumFromThenTo x y bound
-      where
-        bound | fromEnum y >= fromEnum x = maxBound
-              | otherwise                = minBound
-
-    succ (D10_Unsafe 9) = error "D10 overflow"
-    succ (D10_Unsafe x) = D10_Unsafe (succ x)
-
-    pred (D10_Unsafe 0) = error "D10 underflow"
-    pred (D10_Unsafe x) = D10_Unsafe (pred x)
-
----------------------------------------------------
-
--- | Shows base-10 digits using the quasiquoters defined in
--- "Data.D10.Char". A single digit is displayed using 'd10'.
--- A list of digits is displayed using 'd10list'.
-
-instance Integral a => Show (D10 a) where
-    showsPrec _ x = showString "[d10|"     . showsChar x . showString "|]"
-    showList xs   = showString "[d10list|" . showsStr xs . showString "|]"
-
-showsChar :: Integral a => D10 a -> ShowS
-showsChar = showChar . d10Char
-
-showsStr :: Integral a => [D10 a] -> ShowS
-showsStr = appEndo . foldMap (Endo . showsChar)
-
----------------------------------------------------
-
--- | Convert a 'D10' to its underlying 'Char' representation.
---
--- >>> d10Char [d10|7|]
--- '7'
-
-d10Char :: Integral a => D10 a -> Char
-d10Char (D10_Unsafe x) = chr (ord '0' P.+ fromIntegral x)
-
--- | Convert a 'D10' to a 'String'.
---
--- @'d10Str' x = ['d10Char' x]@
---
--- >>> d10Str [d10|7|]
--- "7"
-
-d10Str :: Integral a => D10 a -> String
-d10Str x = [d10Char x]
-
--- | Convert a 'D10' to a 'Natural'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Nat [d10|7|]
--- 7
-
-d10Nat :: Integral a => D10 a -> Natural
-d10Nat = d10Num
-
--- | Convert a 'D10' to an 'Integer'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Integer [d10|7|]
--- 7
-
-d10Integer :: Integral a => D10 a -> Integer
-d10Integer = d10Num
-
--- | Convert a 'D10' to an 'Int'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Int [d10|7|]
--- 7
-
-d10Int :: Integral a => D10 a -> Int
-d10Int = d10Num
-
--- | Convert a 'D10' to any kind of number with a 'Num' instance.
---
--- Specialized versions of this function include 'd10Nat',
--- 'd10Integer', and 'd10Int'.
---
--- >>> d10Num [d10|7|] :: Integer
--- 7
-
-d10Num :: (Integral b, Num a) => D10 b -> a
-d10Num (D10_Unsafe x) = fromIntegral x
-
----------------------------------------------------
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> natMod10 56 :: D10 Int
--- [d10|6|]
-
-natMod10 :: Num a => Natural -> D10 a
-natMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> integerMod10 56 :: D10 Int
--- [d10|6|]
---
--- >>> integerMod10 (-56) :: D10 Int
--- [d10|4|]
-
-integerMod10 :: Num a => Integer -> D10 a
-integerMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> intMod10 56 :: D10 Int
--- [d10|6|]
---
--- >>> intMod10 (-56) :: D10 Int
--- [d10|4|]
-
-intMod10 :: Num a => Int -> D10 a
-intMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given number
--- (whose type must have an instance of the 'Integral' class).
---
--- Specialized versions of this function include 'natMod10',
--- 'integerMod10', and 'intMod10'.
---
--- >>> integralMod10 (56 :: Integer) :: D10 Int
--- [d10|6|]
---
--- >>> integralMod10 ((-56) :: Integer) :: D10 Int
--- [d10|4|]
-
-integralMod10 :: (Num b, Integral a) => a -> D10 b
-integralMod10 x = D10_Unsafe (fromIntegral (x `mod` 10))
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
---
--- 'charD10Fail' is a more general version of this function.
---
--- >>> charD10Maybe '5'
--- Just [d10|5|]
---
--- >>> charD10Maybe 'a'
--- Nothing
-
-charD10Maybe :: Num a => Char -> Maybe (D10 a)
-charD10Maybe x
-        | isD10Char x  =  Just (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
-        | otherwise    =  Nothing
-
--- | Convert a 'String' to a 'D10' if it consists of exactly one
--- character and that character is within the range @'0'@ to @'9'@,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
---
--- 'strD10Fail' is a more general version of this function.
---
--- >>> strD10Maybe "5"
--- Just [d10|5|]
---
--- >>> strD10Maybe "a"
--- Nothing
---
--- >>> strD10Maybe "58"
--- Nothing
-
-strD10Maybe :: Num a => String -> Maybe (D10 a)
-strD10Maybe [x] = charD10Maybe x
-strD10Maybe _   = Nothing
-
--- | Convert a 'String' to a list of 'D10' if all of the characters
--- in the string are within the range @'0'@ to @'9'@, or produce
--- 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
---
--- 'strD10ListFail' is a more general version of this function.
---
--- >>> strD10ListMaybe "5"
--- Just [d10list|5|]
---
--- >>> strD10ListMaybe "a"
--- Nothing
---
--- >>> strD10ListMaybe "58"
--- Just [d10list|58|]
-
-strD10ListMaybe :: Num a => String -> Maybe [D10 a]
-strD10ListMaybe = traverse charD10Maybe
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
---
--- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> natD10Maybe 5
--- Just [d10|5|]
---
--- >>> natD10Maybe 12
--- Nothing
-
-natD10Maybe :: Num a => Natural -> Maybe (D10 a)
-natD10Maybe x
-        | isD10Nat x  =  Just (D10_Unsafe (fromIntegral x))
-        | otherwise   =  Nothing
-
--- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
---
--- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> integerD10Maybe 5
--- Just [d10|5|]
---
--- >>> integerD10Maybe 12
--- Nothing
---
--- >>> integerD10Maybe (-5)
--- Nothing
-
-integerD10Maybe :: Num a => Integer -> Maybe (D10 a)
-integerD10Maybe x
-        | isD10Integer x  =  Just (D10_Unsafe (fromIntegral x))
-        | otherwise       =  Nothing
-
--- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
---
--- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> intD10Maybe 5
--- Just [d10|5|]
---
--- >>> intD10Maybe 12
--- Nothing
---
--- >>> intD10Maybe (-5)
--- Nothing
-
-intD10Maybe :: Num a => Int -> Maybe (D10 a)
-intD10Maybe x
-        | isD10Int x  =  Just (D10_Unsafe (fromIntegral x))
-        | otherwise   =  Nothing
-
--- | Construct a 'D10' from any kind of number with an 'Integral'
--- instance, or produce 'Nothing' if the number falls outside the
--- range 0 to 9.
---
--- @'Data.D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
---
--- Specialized versions of this function include 'natD10Maybe',
--- 'integerD10Maybe', and 'intD10Maybe'.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integralD10Maybe (5 :: Integer)
--- Just [d10|5|]
---
--- >>> integralD10Maybe (12 :: Integer)
--- Nothing
---
--- >>> integralD10Maybe ((-5) :: Integer)
--- Nothing
-
-integralD10Maybe :: (Num b, Integral a) => a -> Maybe (D10 b)
-integralD10Maybe x = integerD10Maybe (toInteger x)
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
---
--- >>> charD10Either '5'
--- Right [d10|5|]
---
--- >>> charD10Either 'a'
--- Left "d10 must be between 0 and 9"
-
-charD10Either :: Num a => Char -> Either String (D10 a)
-charD10Either x
-        | isD10Char x  =  Right (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
-        | otherwise    =  Left "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'Left' with an error message otherwise.
---
--- >>> strD10Either "5"
--- Right [d10|5|]
---
--- >>> strD10Either "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10Either "58"
--- Left "d10 must be a single character"
-
-strD10Either :: Num a => String -> Either String (D10 a)
-strD10Either [x]         =  charD10Either x
-strD10Either _           =  Left "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'Left'
--- with an error message otherwise.
---
--- >>> strD10ListEither "5"
--- Right [d10list|5|]
---
--- >>> strD10ListEither "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10ListEither "58"
--- Right [d10list|58|]
-
-strD10ListEither :: Num a => String -> Either String [D10 a]
-strD10ListEither = traverse charD10Either
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'Left' with an error message otherwise.
---
--- >>> natD10Either 5
--- Right [d10|5|]
---
--- >>> natD10Either 12
--- Left "d10 must be less than 10"
-
-natD10Either :: Num a => Natural -> Either String (D10 a)
-natD10Either x =
-    case (natD10Maybe x) of
-        Just y  -> Right y
-        Nothing -> Left "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> integerD10Either 5
--- Right [d10|5|]
---
--- >>> integerD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> integerD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-integerD10Either :: Num a => Integer -> Either String (D10 a)
-integerD10Either x =
-    case (integerD10Maybe x) of
-        Just y  -> Right y
-        Nothing -> Left "d10 must be between 0 and 9"
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> intD10Either 5
--- Right [d10|5|]
---
--- >>> intD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> intD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-intD10Either :: Num a => Int -> Either String (D10 a)
-intD10Either x =
-    case (intD10Maybe x) of
-        Just y  ->  Right y
-        Nothing ->  Left "d10 must be between 0 and 9"
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'Left'
--- with an error message otherwise.
---
--- >>> integralD10Either (5 :: Integer)
--- Right [d10|5|]
---
--- >>> integralD10Either (12 :: Integer)
--- Left "d10 must be between 0 and 9"
---
--- >>> integralD10Either ((-5) :: Integer)
--- Left "d10 must be between 0 and 9"
-
-integralD10Either :: (Num b, Integral a) => a -> Either String (D10 b)
-integralD10Either x = integerD10Either (toInteger x)
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
---
--- 'charD10Maybe' is a specialized version of this function.
---
--- >>> charD10Fail '5' :: IO (D10 Int)
--- [d10|5|]
---
--- >>> charD10Fail 'a' :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
-
-charD10Fail :: (Num a, MonadFail m) => Char -> m (D10 a)
-charD10Fail x
-        | isD10Char x  =  return (D10_Unsafe (fromIntegral (ord x P.- ord '0')))
-        | otherwise    =  fail "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'fail' with an error message otherwise.
---
--- 'strD10Maybe' is a specialized version of this function.
---
--- >>> strD10Fail "5" :: IO (D10 Int)
--- [d10|5|]
---
--- >>> strD10Fail "a" :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10Fail "58" :: IO (D10 Int)
--- *** Exception: user error (d10 must be a single character)
-
-strD10Fail :: (Num a, MonadFail m) => String -> m (D10 a)
-strD10Fail [x]         =  charD10Fail x
-strD10Fail _           =  fail "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'fail'
--- with an error message otherwise.
---
--- 'strD10ListMaybe' is a specialized version of this function.
---
--- >>> strD10ListFail "5" :: IO [D10 Int]
--- [d10list|5|]
---
--- >>> strD10ListFail "a" :: IO [D10 Int]
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10ListFail "58" :: IO [D10 Int]
--- [d10list|58|]
-
-strD10ListFail :: (Num a, MonadFail m) => String -> m [D10 a]
-strD10ListFail = traverse charD10Fail
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'fail' with an error message otherwise.
---
--- 'natD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> natD10Fail 5 :: IO (D10 Int)
--- [d10|5|]
---
--- >>> natD10Fail 12 :: IO (D10 Int)
--- *** Exception: user error (d10 must be less than 10)
-
-natD10Fail :: (Num a, MonadFail m) => Natural -> m (D10 a)
-natD10Fail x =
-    case (natD10Maybe x) of
-        Just y  -> return y
-        Nothing -> fail "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'fail' with an error message otherwise.
---
--- 'integerD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integerD10Fail 5 :: IO (D10 Int)
--- [d10|5|]
---
--- >>> integerD10Fail 12 :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integerD10Fail (-5) :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integerD10Fail :: (Num a, MonadFail m) => Integer -> m (D10 a)
-integerD10Fail x =
-    case (integerD10Maybe x) of
-        Just y  -> return y
-        Nothing -> fail "d10 must be between 0 and 9"
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'fail' with an error message otherwise.
---
--- 'intD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> intD10Fail 5 :: IO (D10 Int)
--- [d10|5|]
---
--- >>> intD10Fail 12 :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> intD10Fail (-5) :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
-
-intD10Fail :: (Num a, MonadFail m) => Int -> m (D10 a)
-intD10Fail x =
-    case (intD10Maybe x) of
-        Just y  ->  return y
-        Nothing ->  fail "d10 must be between 0 and 9"
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'fail'
--- with an error message otherwise.
---
--- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
--- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
--- 'intD10Fail' are all specialized versions of this function.
---
--- >>> integralD10Fail (5 :: Integer) :: IO (D10 Int)
--- [d10|5|]
---
--- >>> integralD10Fail (12 :: Integer) :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integralD10Fail ((-5) :: Integer) :: IO (D10 Int)
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integralD10Fail :: (Num b, Integral a, MonadFail m) => a -> m (D10 b)
-integralD10Fail x = integerD10Fail (toInteger x)
-
----------------------------------------------------
-
--- | Produces an expression of type @'D10' a@ that can be used
--- in a Template Haskell splice.
---
--- >>> d10Nat $(d10Exp 5)
--- 5
---
--- >>> d10Nat $(d10Exp 12)
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- You may also be interested in 'd10', a quasi-quoter which
--- does something similar.
-
-d10Exp :: Integer -> Q Exp
-d10Exp = integerD10Fail >=> d10Exp'
-
-d10Exp' :: D10 Integer -> Q Exp
-d10Exp' (D10_Unsafe x) = [| D10_Unsafe (fromInteger x) |]
-
--- | Produces an expression of type @['D10' a]@ that can be used
--- in a Template Haskell splice.
---
--- >>> d10Nat <$> $(d10ListExp "")
--- []
---
--- >>> d10Nat <$> $(d10ListExp "5")
--- [5]
---
--- >>> d10Nat <$> $(d10ListExp "58")
--- [5,8]
---
--- >>> d10Nat <$> $(d10ListExp "a")
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- You may also be interested in 'd10list', a quasi-quoter which
--- does something similar.
-
-d10ListExp :: String -> Q Exp
-d10ListExp = strD10ListFail >=> d10ListExp'
-
-d10ListExp' :: [D10 Integer] -> Q Exp
-d10ListExp' xs = [| xs |]
-
----------------------------------------------------
-
--- | Produces a pattern that can be used in a splice
--- to match a particular @'D10' a@ value.
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just $(d10Pat 4) -> "A"
---         Just $(d10Pat 5) -> "B"
---         _                -> "C"
--- >>> :}
--- "B"
---
--- You may wish to use the 'd10' quasi-quoter instead.
-
-d10Pat :: Integer -> Q Pat
-d10Pat = integerD10Fail >=> d10Pat'
-
-d10Pat' :: D10 Integer -> Q Pat
-d10Pat' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ integerL x) |]
-
-d10Pat'' :: Integral a => D10 a -> Q Pat
-d10Pat'' (D10_Unsafe x) = [p| D10_Unsafe $(litP $ integerL $ toInteger x) |]
-
--- | Produces a pattern that can be used in a splice
--- to match a particular list of @'D10' a@ values.
---
--- >>> :{
---       case (strD10ListMaybe "56") of
---         Just $(d10ListPat "42") -> "A"
---         Just $(d10ListPat "56") -> "B"
---         _                       -> "C"
--- >>> :}
--- "B"
---
--- You may wish to use the 'd10list' quasi-quoter instead.
-
-d10ListPat :: String -> Q Pat
-d10ListPat = strD10ListFail >=> d10ListPat'
-
-d10ListPat' :: [D10 Integer] -> Q Pat
-d10ListPat' = foldr (\x p -> [p| $(d10Pat'' x) : $(p) |]) [p| [] |]
-
----------------------------------------------------
-
--- | A single base-10 digit.
---
--- This quasi-quoter, when used as an expression, produces a
--- value of type @'D10' a@.
---
--- >>> d10Nat [d10|5|]
--- 5
---
--- >>> d10Nat [d10|a|]
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- >>> d10Nat [d10|58|]
--- ...
--- ... d10 must be a single character
--- ...
---
--- This quasi-quoter can also be used as a pattern.
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just [d10|4|] -> "A"
---         Just [d10|5|] -> "B"
---         _             -> "C"
--- >>> :}
--- "B"
---
--- >>> :{
---       case (charD10Maybe '5') of
---         Just [d10|x|] -> "A"
---         Just [d10|5|] -> "B"
---         _             -> "C"
--- >>> :}
--- ...
--- ... d10 must be between 0 and 9
--- ...
-
-d10 :: QuasiQuoter
-d10 = QuasiQuoter
-    { quoteExp  = strD10Fail >=> d10Exp'
-    , quotePat  = strD10Fail >=> d10Pat'
-    , quoteType = \_ -> fail "d10 cannot be used in a type context"
-    , quoteDec  = \_ -> fail "d10 cannot be used in a declaration context"
-    }
-
--- | A list of base-10 digits.
---
--- This quasi-quoter, when used as an expression, produces a
--- value of type @['D10' a]@.
---
--- >>> d10Nat <$> [d10list||]
--- []
---
--- >>> d10Nat <$> [d10list|5|]
--- [5]
---
--- >>> d10Nat <$> [d10list|58|]
--- [5,8]
---
--- >>> d10Nat <$> [d10list|a|]
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- This quasi-quoter can also be used as a pattern.
---
--- >>> :{
---       case [d10list|56|] of
---         [d10list|41|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- "B"
---
--- >>> :{
---       case [d10list|56|] of
---         [d10list|4x|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- ...
--- ... d10 must be between 0 and 9
--- ...
-
-d10list :: QuasiQuoter
-d10list = QuasiQuoter
-    { quoteExp  = strD10ListFail >=> d10ListExp'
-    , quotePat  = strD10ListFail >=> d10ListPat'
-    , quoteType = \_ -> fail "d10list cannot be used in a type context"
-    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
-    }
-
----------------------------------------------------
-
--- | Addition modulo 10.
---
--- >>> [d10|2|] + [d10|3|]
--- [d10|5|]
---
--- >>> [d10|6|] + [d10|7|]
--- [d10|3|]
-
-(+) :: Integral a => D10 a -> D10 a -> D10 a
-x + y = intMod10 (d10Int x P.+ d10Int y)
-
--- | Subtraction modulo 10.
---
--- >>> [d10|7|] - [d10|5|]
--- [d10|2|]
---
--- >>> [d10|3|] - [d10|7|]
--- [d10|6|]
-
-(-) :: Integral a => D10 a -> D10 a -> D10 a
-x - y = intMod10 (d10Int x P.- d10Int y)
-
--- | Multiplication modulo 10.
---
--- >>> [d10|2|] * [d10|4|]
--- [d10|8|]
---
--- >>> [d10|7|] * [d10|8|]
--- [d10|6|]
-
-(*) :: Integral a => D10 a -> D10 a -> D10 a
-x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/src/Data/D10/Predicate.hs b/src/Data/D10/Predicate.hs
deleted file mode 100644
--- a/src/Data/D10/Predicate.hs
+++ /dev/null
@@ -1,37 +0,0 @@
--- | Functions to test whether values of various
--- types represent digits in the range /0/ to /9/.
-module Data.D10.Predicate where
-
-import Numeric.Natural (Natural)
-
--- | Determines whether a 'Char' is in the range @'0'@ to @'9'@.
-isD10Char :: Char -> Bool
-isD10Char x = x >= '0' && x <= '9'
-
--- | Determines whether a 'String' consists of a single character
--- and that character is within the range @'0'@ to @'9'@.
-isD10Str :: String -> Bool
-isD10Str [x] = isD10Char x
-isD10Str _   = False
-
--- | Determines whether a 'String' consists entirely of characters
--- that are within the range @'0'@ to @'9'@.
-isD10ListStr :: String -> Bool
-isD10ListStr = all isD10Char
-
--- | Determines whether a 'Natural' is in the range 0 to 9.
-isD10Nat :: Natural -> Bool
-isD10Nat x = x <= 9
-
--- | Determines whether an 'Integer' is in the range 0 to 9.
-isD10Integer :: Integer -> Bool
-isD10Integer x = x >= 0 && x <= 9
-
--- | Determines whether an 'Int' is in the range 0 to 9.
-isD10Int :: Int -> Bool
-isD10Int x = x >= 0 && x <= 9
-
--- | Determines whether a number whose type has an 'Integral'
--- instance is in the range 0 to 9.
-isD10Integral :: Integral a => a -> Bool
-isD10Integral x = isD10Integer (toInteger x)
diff --git a/src/Data/D10/Safe.hs b/src/Data/D10/Safe.hs
deleted file mode 100644
--- a/src/Data/D10/Safe.hs
+++ /dev/null
@@ -1,876 +0,0 @@
--- | Defines a 'D10' type as
--- @'D0' | 'D1' | 'D2' | 'D3' | 'D4' | 'D5' | 'D6' | 'D7' | 'D8' | 'D9'@.
---
--- The following modules define @D10@ types in different ways
--- but are otherwise very similar to this one:
---
--- * "Data.D10.Char"
--- * "Data.D10.Num"
---
--- This module is called "safe" because, in contrast with the
--- alternative representations of a digit defined in the other
--- modules, this 'D10' type does not include any possibility
--- of representing an invalid non-digit value.
-
-module Data.D10.Safe
-    (
-    -- * Type
-      D10 (..)
-    -- $bounded
-    -- $enum
-
-    -- * Quasi-quoters
-    , d10list
-
-    -- * Splice expressions
-    , d10ListExp
-
-    -- * Splice patterns
-    , d10ListPat
-
-    -- * Converting between D10 and Char
-    , d10Char
-    , charD10Maybe
-    , charD10Either
-    , charD10Fail
-
-    -- * Converting between D10 and String
-    , d10Str
-    , strD10Maybe
-    , strD10Either
-    , strD10Fail
-
-    -- * Converting between [D10] and String
-    , strD10ListMaybe
-    , strD10ListEither
-    , strD10ListFail
-
-    -- * Converting between D10 and Natural
-    , d10Nat
-    , natD10Maybe
-    , natD10Either
-    , natD10Fail
-    , natMod10
-
-    -- * Converting between D10 and Integer
-    , d10Integer
-    , integerD10Maybe
-    , integerD10Either
-    , integerD10Fail
-    , integerMod10
-
-    -- * Converting between D10 and Int
-    , d10Int
-    , intD10Maybe
-    , intD10Either
-    , intD10Fail
-    , intMod10
-
-    -- * Converting between D10 and general numeric types
-    , d10Num
-    , integralD10Maybe
-    , integralD10Either
-    , integralD10Fail
-    , integralMod10
-
-    -- * Modular arithmetic
-    , (+), (-), (*)
-
-    ) where
-
--- base
-import Control.Monad      ((>=>))
-import Control.Monad.Fail (MonadFail (fail))
-import Data.Data          (Data)
-import GHC.Generics       (Generic)
-import Numeric.Natural    (Natural)
-import Prelude            hiding (fail, (+), (-), (*))
-
-import qualified Prelude as P
-
--- template-haskell
-import Language.Haskell.TH.Quote  (QuasiQuoter (..))
-import Language.Haskell.TH.Syntax (Exp (..), Lift, Pat (..), Q, dataToPatQ)
-
----------------------------------------------------
-
--- | A whole number between /0/ and /9/.
-
-data D10
-    = D0  -- ^ Zero
-    | D1  -- ^ One
-    | D2  -- ^ Two
-    | D3  -- ^ Three
-    | D4  -- ^ Four
-    | D5  -- ^ Five
-    | D6  -- ^ Six
-    | D7  -- ^ Seven
-    | D8  -- ^ Eight
-    | D9  -- ^ Nine
-    deriving (Bounded, Enum, Eq, Lift, Ord, Show, Data, Generic)
-
--- $bounded
--- ==== Bounded
---
--- >>> minBound :: D10
--- D0
---
--- >>> maxBound :: D10
--- D9
-
--- $enum
--- ==== Enum
---
--- >>> [ D5 .. ]
--- [D5,D6,D7,D8,D9]
---
--- >>> [ D4 .. D7 ]
--- [D4,D5,D6,D7]
---
--- >>> [ D5, D4 .. ]
--- [D5,D4,D3,D2,D1,D0]
---
--- >>> [ D1, D3 .. ]
--- [D1,D3,D5,D7,D9]
---
--- >>> [ minBound .. maxBound ] :: [D10]
--- [D0,D1,D2,D3,D4,D5,D6,D7,D8,D9]
-
----------------------------------------------------
-
--- | Convert a 'D10' to its underlying 'Char' representation.
---
--- >>> d10Char D7
--- '7'
-
-d10Char :: D10 -> Char
-d10Char x =
-    case x of
-        D0 -> '0'
-        D1 -> '1'
-        D2 -> '2'
-        D3 -> '3'
-        D4 -> '4'
-        D5 -> '5'
-        D6 -> '6'
-        D7 -> '7'
-        D8 -> '8'
-        D9 -> '9'
-
--- | Convert a 'D10' to a 'String'.
---
--- @'d10Str' x = ['d10Char' x]@
---
--- >>> d10Str D7
--- "7"
-
-d10Str :: D10 -> String
-d10Str x = [d10Char x]
-
--- | Convert a 'D10' to a 'Natural'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Nat D7
--- 7
-
-d10Nat :: D10 -> Natural
-d10Nat = d10Num
-
--- | Convert a 'D10' to an 'Integer'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Integer D7
--- 7
-
-d10Integer :: D10 -> Integer
-d10Integer = d10Num
-
--- | Convert a 'D10' to an 'Int'.
---
--- 'd10Num' is a more general version of this function.
---
--- >>> d10Int D7
--- 7
-
-d10Int :: D10 -> Int
-d10Int = d10Num
-
--- | Convert a 'D10' to any kind of number with a 'Num' instance.
---
--- Specialized versions of this function include 'd10Nat',
--- 'd10Integer', and 'd10Int'.
---
--- >>> d10Num D7 :: Integer
--- 7
-
-d10Num :: Num a => D10 -> a
-d10Num x =
-    case x of
-        D0 -> 0
-        D1 -> 1
-        D2 -> 2
-        D3 -> 3
-        D4 -> 4
-        D5 -> 5
-        D6 -> 6
-        D7 -> 7
-        D8 -> 8
-        D9 -> 9
-
----------------------------------------------------
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Natural'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> natMod10 56
--- D6
-
-natMod10 :: Natural -> D10
-natMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Integer'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> integerMod10 56
--- D6
---
--- >>> integerMod10 (-56)
--- D4
-
-integerMod10 :: Integer -> D10
-integerMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given 'Int'.
---
--- 'integralMod10' is a more general version of this function.
---
--- >>> intMod10 56
--- D6
---
--- >>> intMod10 (-56)
--- D4
-
-intMod10 :: Int -> D10
-intMod10 = integralMod10
-
--- | The 'D10' which is uniquely congruent modulo 10 to the given number
--- (whose type must have an instance of the 'Integral' class).
---
--- Specialized versions of this function include 'natMod10',
--- 'integerMod10', and 'intMod10'.
---
--- >>> integralMod10 (56 :: Integer)
--- D6
---
--- >>> integralMod10 ((-56) :: Integer)
--- D4
-
-integralMod10 :: Integral a => a -> D10
-integralMod10 x =
-    case (x `mod` 10) of
-        0 -> D0
-        1 -> D1
-        2 -> D2
-        3 -> D3
-        4 -> D4
-        5 -> D5
-        6 -> D6
-        7 -> D7
-        8 -> D8
-        9 -> D9
-        _ -> error "x `mod` 10 is not between 0 and 9"
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Char' x = 'Data.Maybe.isJust' ('charD10Maybe' x)@
---
--- 'charD10Fail' is a more general version of this function.
---
--- >>> charD10Maybe '5'
--- Just D5
---
--- >>> charD10Maybe 'a'
--- Nothing
-
-charD10Maybe :: Char -> Maybe D10
-charD10Maybe x =
-    case x of
-        '0' -> Just D0
-        '1' -> Just D1
-        '2' -> Just D2
-        '3' -> Just D3
-        '4' -> Just D4
-        '5' -> Just D5
-        '6' -> Just D6
-        '7' -> Just D7
-        '8' -> Just D8
-        '9' -> Just D9
-        _   -> Nothing
-
--- | Convert a 'String' to a 'D10' if it consists of exactly one
--- character and that character is within the range @'0'@ to @'9'@,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Str' x = 'Data.Maybe.isJust' ('strD10Maybe' x)@
---
--- 'strD10Fail' is a more general version of this function.
---
--- >>> strD10Maybe "5"
--- Just D5
---
--- >>> strD10Maybe "a"
--- Nothing
---
--- >>> strD10Maybe "58"
--- Nothing
-
-strD10Maybe :: String -> Maybe D10
-strD10Maybe [x] = charD10Maybe x
-strD10Maybe _   = Nothing
-
--- | Convert a 'String' to a list of 'D10' if all of the characters
--- in the string are within the range @'0'@ to @'9'@, or produce
--- 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10ListStr' x = 'Data.Maybe.isJust' ('strD10ListMaybe' x)@
---
--- 'strD10ListFail' is a more general version of this function.
---
--- >>> strD10ListMaybe "5"
--- Just [D5]
---
--- >>> strD10ListMaybe "a"
--- Nothing
---
--- >>> strD10ListMaybe "58"
--- Just [D5,D8]
-
-strD10ListMaybe :: String -> Maybe [D10]
-strD10ListMaybe = traverse charD10Maybe
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Nat' x = 'Data.Maybe.isJust' ('natD10Maybe' x)@
---
--- 'integralD10Maybe', 'natD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> natD10Maybe 5
--- Just D5
---
--- >>> natD10Maybe 12
--- Nothing
-
-natD10Maybe :: Natural -> Maybe D10
-natD10Maybe = integralD10Maybe
-
--- | Convert an 'Integer' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Integer' x = 'Data.Maybe.isJust' ('integerD10Maybe' x)@
---
--- 'integralD10Maybe', 'integerD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> integerD10Maybe 5
--- Just D5
---
--- >>> integerD10Maybe 12
--- Nothing
---
--- >>> integerD10Maybe (-5)
--- Nothing
-
-integerD10Maybe :: Integer -> Maybe D10
-integerD10Maybe = integralD10Maybe
-
--- | Convert an 'Int' to a 'D10' if it is within the range 0 to 9,
--- or produce 'Nothing' otherwise.
---
--- @'Data.D10.Predicate.isD10Int' x = 'Data.Maybe.isJust' ('intD10Maybe' x)@
---
--- 'integralD10Maybe', 'intD10Fail', and 'integralD10Fail'
--- are more general versions of this function.
---
--- >>> intD10Maybe 5
--- Just D5
---
--- >>> intD10Maybe 12
--- Nothing
---
--- >>> intD10Maybe (-5)
--- Nothing
-
-intD10Maybe :: Int -> Maybe D10
-intD10Maybe = integralD10Maybe
-
--- | Construct a 'D10' from any kind of number with an 'Integral'
--- instance, or produce 'Nothing' if the number falls outside the
--- range 0 to 9.
---
--- @'Data.D10.Predicate.isD10Integral' x = 'Data.Maybe.isJust' ('integralD10Maybe' x)@
---
--- Specialized versions of this function include 'natD10Maybe',
--- 'integerD10Maybe', and 'intD10Maybe'.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integralD10Maybe (5 :: Integer)
--- Just D5
---
--- >>> integralD10Maybe (12 :: Integer)
--- Nothing
---
--- >>> integralD10Maybe ((-5) :: Integer)
--- Nothing
-
-integralD10Maybe :: Integral a => a -> Maybe D10
-integralD10Maybe x =
-    case x of
-        0 -> Just D0
-        1 -> Just D1
-        2 -> Just D2
-        3 -> Just D3
-        4 -> Just D4
-        5 -> Just D5
-        6 -> Just D6
-        7 -> Just D7
-        8 -> Just D8
-        9 -> Just D9
-        _ -> Nothing
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'Left' with an error message otherwise.
---
--- >>> charD10Either '5'
--- Right D5
---
--- >>> charD10Either 'a'
--- Left "d10 must be between 0 and 9"
-
-charD10Either :: Char -> Either String D10
-charD10Either x =
-    case x of
-        '0' -> Right D0
-        '1' -> Right D1
-        '2' -> Right D2
-        '3' -> Right D3
-        '4' -> Right D4
-        '5' -> Right D5
-        '6' -> Right D6
-        '7' -> Right D7
-        '8' -> Right D8
-        '9' -> Right D9
-        _   -> Left "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'Left' with an error message otherwise.
---
--- >>> strD10Either "5"
--- Right D5
---
--- >>> strD10Either "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10Either "58"
--- Left "d10 must be a single character"
-
-strD10Either :: String -> Either String D10
-strD10Either [x] = charD10Either x
-strD10Either _   = Left "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'Left'
--- with an error message otherwise.
---
--- >>> strD10ListEither "5"
--- Right [D5]
---
--- >>> strD10ListEither "a"
--- Left "d10 must be between 0 and 9"
---
--- >>> strD10ListEither "58"
--- Right [D5,D8]
-
-strD10ListEither :: String -> Either String [D10]
-strD10ListEither = traverse charD10Either
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'Left' with an error message otherwise.
---
--- >>> natD10Either 5
--- Right D5
---
--- >>> natD10Either 12
--- Left "d10 must be less than 10"
-
-natD10Either :: Natural -> Either String D10
-natD10Either x =
-    case x of
-        0 -> Right D0
-        1 -> Right D1
-        2 -> Right D2
-        3 -> Right D3
-        4 -> Right D4
-        5 -> Right D5
-        6 -> Right D6
-        7 -> Right D7
-        8 -> Right D8
-        9 -> Right D9
-        _ -> Left "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> integerD10Either 5
--- Right D5
---
--- >>> integerD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> integerD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-integerD10Either :: Integer -> Either String D10
-integerD10Either = integralD10Either
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'Left' with an error message otherwise.
---
--- >>> intD10Either 5
--- Right D5
---
--- >>> intD10Either 12
--- Left "d10 must be between 0 and 9"
---
--- >>> intD10Either (-5)
--- Left "d10 must be between 0 and 9"
-
-intD10Either :: Int -> Either String D10
-intD10Either = integralD10Either
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'Left'
--- with an error message otherwise.
---
--- >>> integralD10Either (5 :: Integer)
--- Right D5
---
--- >>> integralD10Either (12 :: Integer)
--- Left "d10 must be between 0 and 9"
---
--- >>> integralD10Either ((-5) :: Integer)
--- Left "d10 must be between 0 and 9"
-
-integralD10Either :: Integral a => a -> Either String D10
-integralD10Either x =
-    case (integralD10Maybe x) of
-        Just y  -> Right y
-        Nothing -> Left "d10 must be between 0 and 9"
-
----------------------------------------------------
-
--- | Convert a 'Char' to a 'D10' if it is within the range
--- @'0'@ to @'9'@, or 'fail' with an error message otherwise.
---
--- 'charD10Maybe' is a specialized version of this function.
---
--- >>> charD10Fail '5' :: IO D10
--- D5
---
--- >>> charD10Fail 'a' :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-charD10Fail :: MonadFail m => Char -> m D10
-charD10Fail x =
-    case x of
-        '0' -> return D0
-        '1' -> return D1
-        '2' -> return D2
-        '3' -> return D3
-        '4' -> return D4
-        '5' -> return D5
-        '6' -> return D6
-        '7' -> return D7
-        '8' -> return D8
-        '9' -> return D9
-        _   -> fail "d10 must be between 0 and 9"
-
--- | Convert a 'String' to a 'D10' if it consists of a single
--- character and that character is within the range @'0'@ to
--- @'9'@, or 'fail' with an error message otherwise.
---
--- 'strD10Maybe' is a specialized version of this function.
---
--- >>> strD10Fail "5" :: IO D10
--- D5
---
--- >>> strD10Fail "a" :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10Fail "58" :: IO D10
--- *** Exception: user error (d10 must be a single character)
-
-strD10Fail :: MonadFail m => String -> m D10
-strD10Fail [x] = charD10Fail x
-strD10Fail _   = fail "d10 must be a single character"
-
--- | Convert a 'String' to a 'D10' if all of the characters in
--- the string fall within the range @'0'@ to @'9'@, or 'fail'
--- with an error message otherwise.
---
--- 'strD10ListMaybe' is a specialized version of this function.
---
--- >>> strD10ListFail "5" :: IO [D10]
--- [D5]
---
--- >>> strD10ListFail "a" :: IO [D10]
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> strD10ListFail "58" :: IO [D10]
--- [D5,D8]
-
-strD10ListFail :: MonadFail m => String -> m [D10]
-strD10ListFail = traverse charD10Fail
-
--- | Convert a 'Natural' to a 'D10' if it is less than 10,
--- or 'fail' with an error message otherwise.
---
--- 'natD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> natD10Fail 5 :: IO D10
--- D5
---
--- >>> natD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be less than 10)
-
-natD10Fail :: MonadFail m => Natural -> m D10
-natD10Fail x =
-    case x of
-        0 -> return D0
-        1 -> return D1
-        2 -> return D2
-        3 -> return D3
-        4 -> return D4
-        5 -> return D5
-        6 -> return D6
-        7 -> return D7
-        8 -> return D8
-        9 -> return D9
-        _ -> fail "d10 must be less than 10"
-
--- | Convert an 'Integer' to a 'D10' if it is within the
--- range 0 to 9, or 'fail' with an error message otherwise.
---
--- 'integerD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> integerD10Fail 5 :: IO D10
--- D5
---
--- >>> integerD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integerD10Fail (-5) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integerD10Fail :: MonadFail m => Integer -> m D10
-integerD10Fail = integralD10Fail
-
--- | Convert an 'Int' to a 'D10' if it is within the range
--- 0 to 9, or 'fail' with an error message otherwise.
---
--- 'intD10Maybe' is a specialized version of this function.
---
--- 'integralD10Fail' is a more general version of this function.
---
--- >>> intD10Fail 5 :: IO D10
--- D5
---
--- >>> intD10Fail 12 :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> intD10Fail (-5) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-intD10Fail :: MonadFail m => Int -> m D10
-intD10Fail = integralD10Fail
-
--- | Convert a number of a type that has an 'Integral' instance
--- to a 'D10' if it falls within the range 0 to 9, or 'fail'
--- with an error message otherwise.
---
--- 'natD10Maybe', 'integerD10Maybe', 'intD10Maybe',
--- 'integralD10Maybe', 'natD10Fail', 'integerD10Fail', and
--- 'intD10Fail' are all specialized versions of this function.
---
--- >>> integralD10Fail (5 :: Integer) :: IO D10
--- D5
---
--- >>> integralD10Fail (12 :: Integer) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
---
--- >>> integralD10Fail ((-5) :: Integer) :: IO D10
--- *** Exception: user error (d10 must be between 0 and 9)
-
-integralD10Fail :: (Integral a, MonadFail m) => a -> m D10
-integralD10Fail x =
-    case (integralD10Maybe x) of
-        Just y  -> return y
-        Nothing -> fail "d10 must be between 0 and 9"
-
----------------------------------------------------
-
--- | Produces an expression of type @['D10']@ that can be used
--- in a Template Haskell splice.
---
--- >>> $(d10ListExp "")
--- []
---
--- >>> $(d10ListExp "5")
--- [D5]
---
--- >>> $(d10ListExp "58")
--- [D5,D8]
---
--- >>> $(d10ListExp "a")
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- You may also be interested in 'd10list', a quasi-quoter which
--- does something similar.
-
-d10ListExp :: String -> Q Exp
-d10ListExp = strD10ListFail >=> d10ListExp'
-
-d10ListExp' :: [D10] -> Q Exp
-d10ListExp' x = [| x |]
-
----------------------------------------------------
-
-d10Pat :: D10 -> Q Pat
-d10Pat = dataToPatQ (const Nothing)
-
--- | Produces a pattern that can be used in a splice
--- to match a particular list of 'D10' values.
---
--- >>> :{
---       case [D5, D6] of
---         $(d10ListPat "42") -> "A"
---         $(d10ListPat "56") -> "B"
---         _                  -> "C"
--- >>> :}
--- "B"
---
--- You may also be interested in 'd10list', a quasi-quoter which
--- does something similar.
-
-d10ListPat :: String -> Q Pat
-d10ListPat = strD10ListFail >=> \xs ->
-  do
-    pats <- traverse d10Pat xs
-    return (ListP pats)
-
-d10ListPat' :: [D10] -> Q Pat
-d10ListPat' xs =
-  do
-    pats <- traverse d10Pat xs
-    return (ListP pats)
-
----------------------------------------------------
-
--- | A list of base-10 digits.
---
--- This quasi-quoter, when used as an expression, produces a
--- value of type @['D10']@.
---
--- >>> [d10list||]
--- []
---
--- >>> [d10list|5|]
--- [D5]
---
--- >>> [d10list|58|]
--- [D5,D8]
---
--- >>> [d10list|a|]
--- ...
--- ... d10 must be between 0 and 9
--- ...
---
--- This quasi-quoter can also be used as a pattern.
---
--- >>> :{
---       case [D5, D6] of
---         [d10list|41|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- "B"
---
--- >>> :{
---       case [D5, D6] of
---         [d10list|4x|] -> "A"
---         [d10list|56|] -> "B"
---         _             -> "C"
--- >>> :}
--- ...
--- ... d10 must be between 0 and 9
--- ...
-
-d10list :: QuasiQuoter
-d10list = QuasiQuoter
-    { quoteExp  = strD10ListFail >=> d10ListExp'
-    , quotePat  = strD10ListFail >=> d10ListPat'
-    , quoteType = \_ -> fail "d10list cannot be used in a type context"
-    , quoteDec  = \_ -> fail "d10list cannot be used in a declaration context"
-    }
-
----------------------------------------------------
-
--- | Addition modulo 10.
---
--- >>> D2 + D3
--- D5
---
--- >>> D6 + D7
--- D3
-
-(+) :: D10 -> D10 -> D10
-x + y = intMod10 (d10Int x P.+ d10Int y)
-
--- | Subtraction modulo 10.
---
--- >>> D7 - D5
--- D2
---
--- >>> D3 - D7
--- D6
-
-(-) :: D10 -> D10 -> D10
-x - y = intMod10 (d10Int x P.- d10Int y)
-
--- | Multiplication modulo 10.
---
--- >>> D2 * D4
--- D8
--- >>> D7 * D8
--- D6
-
-(*) :: D10 -> D10 -> D10
-x * y = intMod10 (d10Int x P.* d10Int y)
diff --git a/test/char.hs b/test/char.hs
--- a/test/char.hs
+++ b/test/char.hs
@@ -1,7 +1,8 @@
 import Prelude hiding (fail, (+), (-), (*))
 
-import Data.D10.Char
-import qualified Data.D10.Predicate as Predicate
+import D10.Char
+import D10.Char.Arithmetic
+import qualified D10.Predicate as Predicate
 
 import Hedgehog
 import qualified Hedgehog.Gen as Gen
diff --git a/test/num.hs b/test/num.hs
--- a/test/num.hs
+++ b/test/num.hs
@@ -1,7 +1,8 @@
 import Prelude hiding (fail, (+), (-), (*))
 
-import Data.D10.Num
-import qualified Data.D10.Predicate as Predicate
+import D10.Num
+import D10.Num.Arithmetic
+import qualified D10.Predicate as Predicate
 
 import Hedgehog
 import qualified Hedgehog.Gen as Gen
diff --git a/test/safe.hs b/test/safe.hs
--- a/test/safe.hs
+++ b/test/safe.hs
@@ -1,7 +1,8 @@
 import Prelude hiding (fail, (+), (-), (*))
 
-import Data.D10.Safe
-import qualified Data.D10.Predicate as Predicate
+import D10.Safe
+import D10.Safe.Arithmetic
+import qualified D10.Predicate as Predicate
 
 import Hedgehog
 import qualified Hedgehog.Gen as Gen
