├── .github └── workflows │ └── core.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Setup.hs ├── flake.lock ├── flake.nix ├── modular-arithmetic.cabal ├── src └── Data │ └── Modular.hs └── test-suite └── DocTest.hs /.github/workflows/core.yml: -------------------------------------------------------------------------------- 1 | # Run tests for every commit/PR/etc 2 | name: Tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - 'main' 8 | - 'master' 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Install Nix 19 | uses: cachix/install-nix-action@v15 20 | 21 | - name: Build + Test (GHC 9.0.2) 22 | run: nix build .#modular-arithmetic_902 23 | 24 | - name: Build + Test (GHC 9.2.4) 25 | run: nix build .#modular-arithmetic_924 26 | 27 | - name: Build + Test (GHC 9.4.4) 28 | run: nix build .#modular-arithmetic_944 29 | 30 | - name: Build + Test (GHC 9.6.1) 31 | run: nix build .#modular-arithmetic_961 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Haskell 2 | .cabal-sandbox/ 3 | cabal.sandbox.config 4 | dist 5 | dist-newstyle 6 | cabal-dev 7 | cabal.project.local 8 | 9 | # Emacs 10 | *~ 11 | 12 | # Direnv 13 | .envrc 14 | .direnv 15 | 16 | # Nix 17 | result -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2.0.0.3 2 | --- 3 | * Fixed build for GHC 9.4+ 4 | 5 | 2.0.0.0 6 | --- 7 | * replaced `Integral` instance with `Fractional` instance (see #8 and #14) 8 | * added a constraint to ensure the type-level modulus is never 0 9 | * made `inv` return `Maybe` instead of raising an error 10 | * misc. refactoring and improvements 11 | 12 | 1.2.1.3 13 | --- 14 | * fixed a name clash with GHC.TypeLits for base >= 4.11.0 15 | 16 | 1.2.1.2 17 | --- 18 | * exported the `/` type operator with `ExplicitNamespaces` enabled to 19 | support GHC 8. Should be backwards compatible through GHC 7.6. 20 | 21 | 1.2.1.1 22 | --- 23 | * added a basic test suite with doctests 24 | 25 | 1.2.1.0 26 | --- 27 | * changed `Integral` implementation: `quotRem` now uses modular inversion! 28 | * added `inv` for modular inversion 29 | * added `SomeMod` data type for modular number with unknown modulus 30 | * added `modVal` and `someModVal` helpers similar to ones in `GHC.TypeLits` 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013–2020, 2 | Tikhon Jelvis , 3 | Nickolay Kudasov 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * Neither the name of Tikhon Jelvis nor the names of other 19 | contributors may be used to endorse or promote products derived 20 | from this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modular Arithmetic 2 | 3 | [![Hackage package](http://img.shields.io/hackage/v/modular-arithmetic.svg)](http://hackage.haskell.org/package/modular-arithmetic) 4 | 5 | 6 | This package provides a type for integers modulo some constant, usually written as ℤ/n. 7 | 8 | Here is a quick example: 9 | 10 | ``` 11 | >>> 10 * 11 :: ℤ/7 12 | 5 13 | ``` 14 | 15 | It also works correctly with negative numeric literals: 16 | 17 | ``` 18 | >>> (-10) * 11 :: ℤ/7 19 | 2 20 | ``` 21 | 22 | Modular division is an inverse of modular multiplication. 23 | It is defined when divisor is coprime to modulus: 24 | 25 | ``` 26 | >>> 7 `div` 3 :: ℤ/16 27 | 13 28 | >>> 3 * 13 :: ℤ/16 29 | 7 30 | ``` 31 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1685518550, 9 | "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1686263882, 24 | "narHash": "sha256-p5shz5DDVaqISuCH5L4roYP0uz+RNbnTgsUWsvxclAc=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "a164b42bb3ba65f43253a06193fc22d301c6d012", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "repo": "nixpkgs", 33 | "type": "github" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs" 40 | } 41 | }, 42 | "systems": { 43 | "locked": { 44 | "lastModified": 1681028828, 45 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 46 | "owner": "nix-systems", 47 | "repo": "default", 48 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "nix-systems", 53 | "repo": "default", 54 | "type": "github" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A type for integers modulo some constant."; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem (system: 11 | let 12 | pkgs = nixpkgs.legacyPackages.${system}; 13 | haskellPackages = pkgs.haskellPackages; 14 | 15 | package = ghc: 16 | pkgs.haskell.packages."${ghc}".developPackage { 17 | name = "modular-arithmetic"; 18 | root = ./.; 19 | 20 | source-overrides = { 21 | typelits-witnesses = "0.4.0.0"; 22 | }; 23 | 24 | overrides = self: super: with pkgs.haskell.lib; { }; 25 | }; 26 | 27 | shellFor = ghc: 28 | pkgs.haskell.packages."${ghc}".shellFor { 29 | packages = p: [ (package ghc) ]; 30 | 31 | buildInputs = with haskellPackages; [ 32 | cabal-install 33 | ghcid 34 | haskell-language-server 35 | 36 | pkgs.haskellPackages.cabal-fmt 37 | pkgs.nixpkgs-fmt 38 | ]; 39 | }; 40 | in 41 | rec { 42 | # TODO: cleaner way to manage multiple GHC versions... 43 | packages = { 44 | modular-arithmetic_961 = package "ghc961"; 45 | modular-arithmetic_944 = package "ghc944"; 46 | modular-arithmetic_924 = package "ghc924"; 47 | modular-arithmetic_902 = package "ghc902"; 48 | }; 49 | 50 | devShells = { 51 | modular-arithmetic_961 = shellFor "ghc961"; 52 | modular-arithmetic_944 = shellFor "ghc944"; 53 | modular-arithmetic_924 = shellFor "ghc924"; 54 | modular-arithmetic_902 = shellFor "ghc902"; 55 | }; 56 | 57 | defaultPackage = packages.modular-arithmetic_961; 58 | devShell = devShells.modular-arithmetic_961; 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /modular-arithmetic.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: modular-arithmetic 3 | version: 2.0.0.3 4 | synopsis: A type for integers modulo some constant. 5 | 6 | description: A convenient type for working with integers modulo some constant. It saves you from manually wrapping numeric operations all over the place and prevents a range of simple mistakes. @Integer `Mod` 7@ is the type of integers (mod 7) backed by @Integer@. 7 | 8 | We also have some cute syntax for these types like @ℤ/7@ for integers modulo 7. 9 | 10 | homepage: https://github.com/TikhonJelvis/modular-arithmetic 11 | bug-reports: https://github.com/TikhonJelvis/modular-arithmetic/issues 12 | license: BSD-3-Clause 13 | license-file: LICENSE 14 | author: Tikhon Jelvis 15 | maintainer: Tikhon Jelvis 16 | category: Math 17 | build-type: Simple 18 | extra-doc-files: README.md 19 | , CHANGELOG.md 20 | 21 | source-repository head 22 | type: git 23 | location: git://github.com/TikhonJelvis/modular-arithmetic.git 24 | 25 | library 26 | hs-source-dirs: src 27 | ghc-options: -Wall 28 | default-language: Haskell2010 29 | exposed-modules: Data.Modular 30 | build-depends: base >4.9 && <5 31 | if impl(ghc < 9.2.1) 32 | build-depends: typelits-witnesses <0.5 33 | 34 | test-suite examples 35 | hs-source-dirs: test-suite, src 36 | main-is: DocTest.hs 37 | default-language: Haskell2010 38 | type: exitcode-stdio-1.0 39 | build-depends: base >4.9 && <5 40 | , doctest >= 0.9 && <0.22 41 | if impl(ghc < 9.2.1) 42 | build-depends: typelits-witnesses <0.5 43 | -------------------------------------------------------------------------------- /src/Data/Modular.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE AllowAmbiguousTypes #-} 2 | {-# LANGUAGE CPP #-} 3 | {-# LANGUAGE ConstraintKinds #-} 4 | {-# LANGUAGE DataKinds #-} 5 | {-# LANGUAGE ExplicitNamespaces #-} 6 | {-# LANGUAGE GADTs #-} 7 | {-# LANGUAGE KindSignatures #-} 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TypeApplications #-} 10 | {-# LANGUAGE TypeFamilies #-} 11 | {-# LANGUAGE TypeOperators #-} 12 | {-# LANGUAGE UndecidableInstances #-} 13 | 14 | 15 | -- | 16 | -- Types for working with integers modulo some constant. 17 | module Data.Modular ( 18 | -- $doc 19 | 20 | -- * Preliminaries 21 | -- $setup 22 | 23 | -- * Modular arithmetic 24 | Mod, Modulus, 25 | unMod, toMod, toMod', 26 | inv, type (/)(), ℤ, 27 | modVal, SomeMod, someModVal 28 | ) where 29 | 30 | import Control.Arrow (first) 31 | 32 | import Data.Proxy (Proxy (..)) 33 | import Data.Ratio (denominator, numerator, (%)) 34 | 35 | import Text.Printf (printf) 36 | 37 | #if MIN_VERSION_base(4,11,0) 38 | import GHC.TypeLits hiding (Mod) 39 | #else 40 | import GHC.TypeLits 41 | #endif 42 | 43 | #if !MIN_VERSION_base(4,16,0) 44 | import Data.Type.Equality ((:~:) (..)) 45 | 46 | import GHC.TypeLits.Compare ((%<=?), (:<=?) (LE, NLE)) 47 | import GHC.TypeLits.Witnesses (SNat (SNat)) 48 | #endif 49 | 50 | -- $setup 51 | -- 52 | -- To use type level numeric literals you need to enable 53 | -- the @DataKinds@ extension: 54 | -- 55 | -- >>> :set -XDataKinds 56 | -- 57 | -- To use infix syntax for @'Mod'@ or the @/@ synonym, 58 | -- enable @TypeOperators@: 59 | -- 60 | -- >>> :set -XTypeOperators 61 | -- 62 | -- To use type applications with @'toMod'@ and friends: 63 | -- 64 | -- >>> :set -XTypeApplications 65 | -- 66 | 67 | -- $doc 68 | -- 69 | -- @'Mod'@ and its synonym @/@ let you wrap arbitrary numeric types 70 | -- in a modulus. To work with integers (mod 7) backed by @'Integer'@, 71 | -- you could use one of the following equivalent types: 72 | -- 73 | -- > Mod Integer 7 74 | -- > Integer `Mod` 7 75 | -- > Integer/7 76 | -- > ℤ/7 77 | -- 78 | -- (@'ℤ'@ is a synonym for @'Integer'@ provided by this library. In 79 | -- Emacs, you can use the TeX input mode to type it with @\\Bbb{Z}@.) 80 | -- 81 | -- The usual numeric typeclasses are defined for these types. You can 82 | -- always extract the underlying value with @'unMod'@. 83 | -- 84 | -- Here is a quick example: 85 | -- 86 | -- >>> 10 * 11 :: ℤ/7 87 | -- 5 88 | -- 89 | -- It also works correctly with negative numeric literals: 90 | -- 91 | -- >>> (-10) * 11 :: ℤ/7 92 | -- 2 93 | -- 94 | -- Modular division is an inverse of modular multiplication. 95 | -- It is defined when divisor is coprime to modulus: 96 | -- 97 | -- >>> 7 / 3 :: ℤ/16 98 | -- 13 99 | -- >>> 3 * 13 :: ℤ/16 100 | -- 7 101 | -- 102 | -- Note that it raises an exception if the divisor is *not* coprime to 103 | -- the modulus: 104 | -- 105 | -- >>> 7 / 4 :: ℤ/16 106 | -- *** Exception: Cannot invert 4 (mod 16): not coprime to modulus. 107 | -- ... 108 | -- 109 | -- To use type level numeric literals you need to enable the 110 | -- @DataKinds@ extension and to use infix syntax for @Mod@ or the @/@ 111 | -- synonym, you need @TypeOperators@. 112 | 113 | -- | Wraps an underlying @Integeral@ type @i@ in a newtype annotated 114 | -- with the bound @n@. 115 | newtype i `Mod` (n :: Nat) = Mod i deriving (Eq, Ord) 116 | 117 | -- | Extract the underlying integral value from a modular type. 118 | -- 119 | -- >>> unMod (10 :: ℤ/4) 120 | -- 2 121 | unMod :: i `Mod` n -> i 122 | unMod (Mod i) = i 123 | 124 | -- | A synonym for @Mod@, inspired by the ℤ/n syntax from mathematics. 125 | type (/) = Mod 126 | 127 | -- | A synonym for Integer, also inspired by the ℤ/n syntax. 128 | type ℤ = Integer 129 | 130 | -- | The modulus has to be a non-zero type-level natural number. 131 | type Modulus n = (KnownNat n, 1 <= n) 132 | 133 | -- | Helper function to get the modulus of a @ℤ/n@ as a value. Used 134 | -- with type applications: 135 | -- 136 | -- >>> modulus @5 137 | -- 5 138 | -- 139 | modulus :: forall n i. (Integral i, Modulus n) => i 140 | modulus = fromInteger $ natVal (Proxy :: Proxy n) 141 | 142 | -- | Injects a value of the underlying type into the modulus type, 143 | -- wrapping as appropriate. 144 | -- 145 | -- If @n@ is ambiguous, you can specify it with @TypeApplications@: 146 | -- 147 | -- >>> toMod @6 10 148 | -- 4 149 | -- 150 | -- Note that @n@ cannot be 0. 151 | toMod :: forall n i. (Integral i, Modulus n) => i -> i `Mod` n 152 | toMod i = Mod $ i `mod` (modulus @n) 153 | 154 | -- | Convert an integral number @i@ into a @'Mod'@ value with the 155 | -- type-level modulus @n@ specified with a proxy argument. 156 | -- 157 | -- This lets you use 'toMod' without @TypeApplications@ in contexts 158 | -- where @n@ is ambiguous. 159 | modVal :: forall i proxy n. (Integral i, Modulus n) 160 | => i 161 | -> proxy n 162 | -> Mod i n 163 | modVal i _ = toMod i 164 | 165 | -- | Wraps an integral number, converting between integral types. 166 | toMod' :: forall n i j. (Integral i, Integral j, Modulus n) 167 | => i 168 | -> j `Mod` n 169 | toMod' i = toMod . fromIntegral $ i `mod` (modulus @n) 170 | 171 | instance Show i => Show (i `Mod` n) where show (Mod i) = show i 172 | instance (Read i, Integral i, Modulus n) => Read (i `Mod` n) 173 | where readsPrec prec = map (first toMod) . readsPrec prec 174 | 175 | instance (Integral i, Modulus n) => Num (i `Mod` n) where 176 | fromInteger = toMod . fromInteger 177 | 178 | Mod i₁ + Mod i₂ = toMod $ i₁ + i₂ 179 | Mod i₁ * Mod i₂ = toMod $ i₁ * i₂ 180 | 181 | abs (Mod i) = toMod $ abs i 182 | signum (Mod i) = toMod $ signum i 183 | negate (Mod i) = toMod $ negate i 184 | 185 | instance (Integral i, Modulus n) => Enum (i `Mod` n) where 186 | toEnum = fromIntegral 187 | fromEnum = fromIntegral . unMod 188 | 189 | -- implementation straight from the report 190 | enumFrom x = enumFromTo x maxBound 191 | enumFromThen x y = enumFromThenTo x y bound 192 | where 193 | bound | fromEnum y >= fromEnum x = maxBound 194 | | otherwise = minBound 195 | 196 | instance (Integral i, Modulus n) => Bounded (i `Mod` n) where 197 | maxBound = Mod $ pred (modulus @n) 198 | minBound = 0 199 | 200 | instance (Integral i, Modulus n) => Real (i `Mod` n) where 201 | toRational (Mod i) = toInteger i % 1 202 | 203 | -- | Division uses modular inverse 'inv' so it is only possible to 204 | -- divide by numbers coprime to @n@. 205 | -- 206 | -- >>> 1 / 3 :: ℤ/7 207 | -- 5 208 | -- >>> 3 * 5 :: ℤ/7 209 | -- 1 210 | -- 211 | -- >>> 2 / 5 :: ℤ/7 212 | -- 6 213 | -- >>> 5 * 6 :: ℤ/7 214 | -- 2 215 | -- 216 | -- Dividing by a number that is not coprime to @n@ will raise an 217 | -- error. Use 'inv' directly if you want to avoid this. 218 | -- 219 | -- >>> 2 / 7 :: ℤ/7 220 | -- *** Exception: Cannot invert 0 (mod 7): not coprime to modulus. 221 | -- ... 222 | -- 223 | instance (Integral i, Modulus n) => Fractional (i `Mod` n) where 224 | fromRational r = 225 | fromInteger (numerator r) / fromInteger (denominator r) 226 | recip i = unwrap $ inv i 227 | where 228 | unwrap (Just x) = x 229 | unwrap Nothing = 230 | let i' = toInteger $ unMod i 231 | bound' = modulus @n @Integer 232 | in error $ 233 | printf "Cannot invert %d (mod %d): not coprime to modulus." i' bound' 234 | 235 | -- | The modular inverse. 236 | -- 237 | -- >>> inv 3 :: Maybe (ℤ/7) 238 | -- Just 5 239 | -- >>> 3 * 5 :: ℤ/7 240 | -- 1 241 | -- 242 | -- Note that only numbers coprime to @n@ have an inverse modulo @n@: 243 | -- 244 | -- >>> inv 6 :: Maybe (ℤ/15) 245 | -- Nothing 246 | -- 247 | inv :: forall n i. (Modulus n, Integral i) => (i/n) -> Maybe (i/n) 248 | inv (Mod k) = toMod . snd <$> inv' (modulus @n) k 249 | where 250 | -- backwards Euclidean algorithm 251 | inv' _ 0 = Nothing 252 | inv' _ 1 = Just (0, 1) 253 | inv' n x = do 254 | let (q, r) = n `quotRem` x 255 | (q', r') <- inv' x r 256 | pure (r', q' - r' * q) 257 | 258 | -- | A modular number with an unknown modulus. 259 | -- 260 | -- Conceptually @SomeMod i = ∃n. i/n@. 261 | data SomeMod i where 262 | SomeMod :: forall i (n :: Nat). Modulus n => Mod i n -> SomeMod i 263 | 264 | -- | Shows both the number *and* its modulus: 265 | -- 266 | -- >>> show (someModVal 10 4) 267 | -- "Just (someModVal 2 4)" 268 | -- 269 | -- This doesn't *quite* follow the rule that the show instance should 270 | -- be a Haskell expression that evaluates to the given 271 | -- value—'someModVal' returns a 'Maybe'—but this seems like the 272 | -- closest we can reasonably get. 273 | instance Show i => Show (SomeMod i) where 274 | showsPrec p (SomeMod (x :: i/n)) = showParen (p > 10) $ 275 | showString $ printf "someModVal %s %d" (show x) (modulus @n @Integer) 276 | 277 | -- | Convert an integral number @i@ into @'SomeMod'@ with the modulus 278 | -- given at runtime. 279 | -- 280 | -- That is, given @i :: ℤ@, @someModVal i modulus@ is equivalent to @i :: 281 | -- ℤ/modulus@ except we don't know @modulus@ statically. 282 | -- 283 | -- >>> someModVal 10 4 284 | -- Just (someModVal 2 4) 285 | -- 286 | -- Will return 'Nothing' if the modulus is 0 or negative: 287 | -- 288 | -- >>> someModVal 10 (-10) 289 | -- Nothing 290 | -- 291 | -- >>> someModVal 10 0 292 | -- Nothing 293 | -- 294 | someModVal :: Integral i 295 | => i 296 | -- ^ Underlying integer @i@ 297 | -> Integer 298 | -- ^ Modulus @n@ 299 | -> Maybe (SomeMod i) 300 | someModVal i n = do 301 | SomeNat (_ :: Proxy n) <- someNatVal n 302 | #if MIN_VERSION_base(4,16,0) 303 | case Proxy @1 `cmpNat` Proxy @n of 304 | LTI -> pure $ SomeMod $ toMod @n i 305 | EQI -> pure $ SomeMod $ toMod @n i 306 | GTI -> Nothing 307 | #else 308 | case SNat @1 %<=? SNat @n of 309 | LE Refl -> pure $ SomeMod $ toMod @n i 310 | NLE _ _ -> Nothing 311 | #endif 312 | -------------------------------------------------------------------------------- /test-suite/DocTest.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Test.DocTest (doctest) 4 | 5 | main :: IO () 6 | main = doctest ["src/Data/Modular.hs"] 7 | --------------------------------------------------------------------------------