typed-digits (empty) → 0.1.0.0
raw patch · 10 files changed
+384/−0 lines, 10 filesdep +Globdep +QuickCheckdep +basesetup-changed
Dependencies added: Glob, QuickCheck, base, doctest, hspec, hspec-discover, nats, singletons, typed-digits
Files
- ChangeLog.md +13/−0
- LICENSE +19/−0
- README.md +31/−0
- Setup.hs +2/−0
- doctest/Main.hs +19/−0
- src/Data/TypedDigits.hs +30/−0
- src/Data/TypedDigits/Internal.hs +121/−0
- test/Data/TypedDigitsSpec.hs +76/−0
- test/Spec.hs +1/−0
- typed-digits.cabal +72/−0
+ ChangeLog.md view
@@ -0,0 +1,13 @@+# Changelog for typed-digits++Notable changes to this project will be documented in this file.++## Unreleased changes++None.++## 0.1.0.0 - 2019-12-12++Alpha release.++
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright 2019 Arran D. Stewart++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to+deal in the Software without restriction, including without limitation the+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or+sell copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS+IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,31 @@+[](https://travis-ci.org/arranstewart/hs-typed-digits)+[](https://hackage.haskell.org/package/typed-digits)++# typed-digits++Provides a Digit type, with the base of the digit available+as a type-level Nat. Thus, it can be guaranteed at compile-time+that digits of different bases can't be added together.++## Usage example++For more convenient provision of type-level parameters,+it's recommended to use the `DataKinds` and `TypeApplications` extensions.+Then, you can give type-level parameters using `@`, as seen below, instead of+giving the full type.++```haskell+>>> :set -XDataKinds -XTypeApplications+>>> import Data.TypedDigits+>>> let d = digit @9 3+>>> d+Just 3 (base 9)+```++## Source tree contents ++- `src`: Library source files+- `test`: Unit tests+- `doctest`: Documentation tests using [`doctest`](https://github.com/sol/doctest)++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ doctest/Main.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE CPP #-}++import Test.DocTest+import System.FilePath.Glob++-- doctest >= 0.15.0 adds useful "--verbose" flag+#if MIN_VERSION_doctest(0,15,0)+hasVerbose = True+#else+hasVerbose = False+#endif++main = do+ srcFiles <- globDir1 (compile "**/*.hs") "src"+ let doctestOpts = if hasVerbose+ then ["-isrc", "--verbose"]+ else ["-isrc"]+ doctest $ doctestOpts ++ srcFiles+
+ src/Data/TypedDigits.hs view
@@ -0,0 +1,30 @@+{-|++Module : Data.TypedDigits+Description : Digits, indexed by their base at the type level+Portability : portable++= Description++Digits, indexed by their base at the type level.++-}++module Data.TypedDigits (+ Digit+ -- * Constructing 'Digits'+ , digit+ , digit'+ -- * Querying 'Digits'+ , getVal + , getBase+ , getBaseT+ -- * Arithmetic operations on 'Digits'+ , (<+>)+ , (<->)+ , KnownNat+ )+ where++import Data.TypedDigits.Internal+
+ src/Data/TypedDigits/Internal.hs view
@@ -0,0 +1,121 @@++{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}++{-|++Module : Data.TypedDigits.Internal+Description : Data.TypedDigits internals+Portability : portable++This module is considered internal, and may change+without warning.+The Package Versioning Policy does not apply to it.++= Description++Digits, indexed by their base at the type level.++-}++module Data.TypedDigits.Internal (+ module Data.TypedDigits.Internal+ , KnownNat + )+ where++import Data.Maybe+import Data.Proxy+import Data.Singletons.TypeLits (Nat, Sing(..), KnownNat )+import Data.Singletons (singByProxy, SingI(..), fromSing )++-- | digits, indexed by their base at the type level+newtype Digit (base :: Nat) = Digit { getVal :: Int }++-- | @getBaseT p@: if @p@ is of some type @a base@,+-- then reflect the base into a value.+--+-- Useful for getting the base, just given a 'Proxy'; e.g.:+--+-- >>> :set -XDataKinds+-- >>> :set -XTypeApplications+-- >>> let p = Proxy :: Proxy 3+-- >>> getBaseT p+-- 3+getBaseT :: forall a base . (KnownNat base) => a base -> Int+getBaseT _ = fromIntegral $ fromSing s+ where+ s :: Sing base+ s = singByProxy (Proxy :: Proxy base)+++-- | @digit x@: construct a digit. 0 <= @x@ < base must hold+-- true.+--+-- Nicer if TypeApplications is enabled, then you can say:+--+-- >>> :set -XDataKinds+-- >>> :set -XTypeApplications+-- >>> digit @9 8+-- Just 8 (base 9)+digit :: forall base . (KnownNat base) => Int -> Maybe (Digit base)+digit x = + if x >= 0 && x < fromIntegral (getBaseT (Proxy :: Proxy base))+ then Just $ Digit x+ else Nothing++-- | Like 'digit', but throws an 'ErrorCall' if the value is+-- out of range.+-- +-- sample usage:+-- +-- >>> :set -XDataKinds+-- >>> :set -XTypeApplications+-- >>> digit' @9 7+-- 7 (base 9)+digit' :: KnownNat base => Int -> Digit base+digit' x = fromMaybe (error "bad value for base") (digit x)++-- get the Sing type fore the base, given some digit+digitSing :: KnownNat base => Digit base -> Sing base+digitSing Digit{} =+ sing++-- | @getBase d@: return the base of the digit @d@+getBase :: KnownNat base => Digit base -> Int+getBase d = fromIntegral $ fromSing (digitSing d)++deriving instance Eq (Digit base)+deriving instance Ord (Digit base)++instance (KnownNat base) => Show (Digit base) where+ show d = show (getVal d) ++ " (base " ++ show (getBase d) ++ ")"++instance KnownNat base => Enum (Digit base) where+ toEnum = digit'+ fromEnum = getVal++instance (KnownNat base) => Bounded (Digit base) where+ minBound = digit' 0++ maxBound = digit' $ n - 1+ where+ n = getBaseT (Proxy :: Proxy base)++-- | Making 'Digit' an instance of 'Num' does not seem+-- sensible, but it's nevertheless useful to be able to+-- add and subtract digits, so '<+>' and '<->' are provided+-- for this.+--+-- @a <+> b@: Add @a@ and @b@. Throws an 'ErrorCall' if the+-- result is out of range.+(<+>) :: KnownNat base => Digit base -> Digit base -> Digit base+a <+> b = digit' $ getVal a + getVal b++-- | @a <-> b@: Subtract @b@ from @a@. Throws an 'ErrorCall' if the+-- result is out of range.+(<->) :: KnownNat base => Digit base -> Digit base -> Digit base+a <-> b = digit' $ getVal a - getVal b+
+ test/Data/TypedDigitsSpec.hs view
@@ -0,0 +1,76 @@++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-}++module Data.TypedDigitsSpec (main, spec) where+++import Test.Hspec+import Test.QuickCheck++import Control.Monad+import Data.Proxy++import Data.TypedDigits+++--isInverseOf :: Eq a => (a -> b) -> (b -> a) -> a -> Bool+--isInverseOf f g x =+-- g (f x) == x+--+--+--+--sameAs :: Eq b => (a -> b) -> (a -> b) -> a -> Bool+--sameAs f g x =+-- f x == g x+--+--dflor = D.digits `sameAs` DD.digits 10+--+-- `main` is here so that this module can be run from GHCi on its own. It is+-- not needed for automatic spec discovery.+main :: IO ()+main = hspec spec+--+--digitsImpls :: [(String, Int -> [Int])]+--digitsImpls = [+-- ("digits", D.digits)+-- , ("digitsIter", D.digitsIter)+-- , ("digitsPFree", D.digitsPFree)+-- ]++-- Let's do things in base 12.+type TestBase1 = 12++testBase1 :: Int+testBase1 = fromIntegral $ getBaseT (Proxy :: Proxy TestBase1)++-- digits can be added - up to their valid range+propAddable :: Property+propAddable =+ forAll (choose (0, testBase1-1)) $ \x ->+ let y = testBase1 - x - 1+ + xd = digit' @TestBase1 x+ yd = digit' @TestBase1 y+ in getVal (xd <+> yd) == testBase1 - 1+++spec :: Spec+spec = do + describe "Digits" $ do+ it "can be added (up to their valid range)" $+ property propAddable+ it "have minBound 0" $ + getVal (minBound :: Digit TestBase1) `shouldBe` 0+ it "have maxBound = base - 1" $ + getVal (maxBound :: Digit TestBase1) `shouldBe` (testBase1 - 1)+ describe "digit" $ do+ it "yields Nothing if passed a negative" $+ digit @TestBase1 (-1) `shouldBe` Nothing+ it "yields Nothing if passed the base itself" $+ digit @TestBase1 testBase1 `shouldBe` Nothing+ it "yields the corresponding digit if passed anything between" $+ forM_ [0 .. testBase1 - 1] $ \n -> + let x = digit @TestBase1 n+ in fmap getVal x `shouldBe` Just n+
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ typed-digits.cabal view
@@ -0,0 +1,72 @@+name: typed-digits+version: 0.1.0.0+synopsis: Digits, indexed by their base at the type level+description: Digits, indexed by their base at the type level.+ .+ Please see the README, for more details, available on GitHub at <https://github.com/arranstewart/hs-typed-digits#readme>.+category: Data+homepage: https://github.com/arranstewart/hs-typed-digits#readme+bug-reports: https://github.com/arranstewart/hs-typed-digits/issues+author: Arran Stewart+maintainer: haskell-maintenance@arranstewart.info+copyright: 2019 Arran Stewart+license: MIT+license-file: LICENSE+build-type: Simple+extra-source-files:+ README.md+ ChangeLog.md+cabal-version: >= 1.10+tested-with: GHC ==8.6.5 || ==8.4.4 || ==8.2.2++source-repository head+ type: git+ location: https://github.com/arranstewart/hs-typed-digits++Flag Noisy+ Description: Enable more compilation warnings+ Manual: True+ Default: False++library+ exposed-modules:+ Data.TypedDigits+ Data.TypedDigits.Internal+ hs-source-dirs:+ src+ build-depends:+ base >=4.7 && <5+ , nats+ , singletons >= 2.3+ default-language: Haskell98+ if flag(Noisy)+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints++test-suite test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Data.TypedDigitsSpec+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , typed-digits+ , hspec+ , hspec-discover+ , QuickCheck+ default-language: Haskell98++test-suite doctest+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs:+ doctest+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , typed-digits+ , doctest+ , Glob+ default-language: Haskell98