packages feed

lean-peano (empty) → 0.1.0.0

raw patch · 9 files changed

+575/−0 lines, 9 filesdep +QuickCheckdep +basedep +base-compatbuild-type:Customsetup-changed

Dependencies added: QuickCheck, base, base-compat, deepseq, doctest, hedgehog, lean-peano, template-haskell

Files

+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for lean-peano++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2020++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Author name here nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,1 @@+# lean-peano
+ Setup.hs view
@@ -0,0 +1,7 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++import Distribution.Extra.Doctest ( defaultMainWithDoctests )+main :: IO ()+main = defaultMainWithDoctests "doctests"
+ doctest/doctests.hs view
@@ -0,0 +1,14 @@+module Main where++import Build_doctests (flags, pkgs, module_sources)+import Data.Foldable (traverse_)+import System.Environment.Compat (unsetEnv)+import Test.DocTest (doctest)++main :: IO ()+main = do+    traverse_ putStrLn args+    unsetEnv "GHC_ENVIRONMENT"+    doctest args+  where+    args = flags ++ pkgs ++ module_sources
+ lean-peano.cabal view
@@ -0,0 +1,73 @@+cabal-version: >= 1.12++-- This file has been generated from package.yaml by hpack version 0.31.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 64e91e616abbba7139ad4f000d8a6a1505b2a315175f0489493a319d6a76ac89++name:           lean-peano+version:        0.1.0.0+description:    Please see the README on GitHub at <https://github.com/githubuser/lean-peano#readme>+homepage:       https://github.com/oisdk/lean-peano#readme+bug-reports:    https://github.com/oisdk/lean-peano/issues+author:         Donnacha Oisín Kidney+maintainer:     mail@doisinkidney.com+copyright:      2020 Donnacha Oisín Kidney+license:        MIT+license-file:   LICENSE+build-type:     Custom+extra-source-files:+    README.md+    ChangeLog.md++source-repository head+  type: git+  location: https://github.com/oisdk/lean-peano++custom-setup+  setup-depends:+      base+    , Cabal+    , cabal-doctest  >=1.0.6 && <1.1++library+  exposed-modules:+      Numeric.Peano+      Numeric.Peano.Typelevel+  hs-source-dirs:+      src+  ghc-options: -Wall -fwarn-incomplete-record-updates -fwarn-incomplete-uni-patterns -fwarn-redundant-constraints -Wcompat+  build-depends:+      base >=4.7 && <5+    , deepseq+  default-language: Haskell2010++test-suite doctests+  type: exitcode-stdio-1.0+  main-is: doctests.hs+  hs-source-dirs:+      doctest+  ghc-options: -Wall -fwarn-incomplete-record-updates -fwarn-incomplete-uni-patterns -fwarn-redundant-constraints -Wcompat -threaded+  build-depends:+      QuickCheck+    , base+    , base-compat+    , deepseq+    , doctest+    , lean-peano+    , template-haskell+  default-language: Haskell2010++test-suite lean-peano-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs:+      test+  ghc-options: -Wall -fwarn-incomplete-record-updates -fwarn-incomplete-uni-patterns -fwarn-redundant-constraints -Wcompat -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base+    , deepseq+    , hedgehog+    , lean-peano+  default-language: Haskell2010
+ src/Numeric/Peano.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE BangPatterns         #-}+{-# LANGUAGE DeriveDataTypeable   #-}+{-# LANGUAGE DeriveGeneric        #-}++-- | Peano numerals. Effort is made to make them as efficient as+-- possible, and as lazy as possible, but they are many orders of+-- magnitude less efficient than machine integers. They are primarily+-- used for type-level programming, and occasionally for calculations+-- which can be short-circuited.+--+-- For instance, to check if two lists are the same length, you could+-- write:+--+-- @+-- 'length' xs == 'length' ys+-- @+--+-- But this unnecessarily traverses both lists. The more efficient+-- version, on the other hand, is less clear:+--+-- @+-- sameLength [] [] = True+-- sameLength (_:xs) (_:ys) = sameLength xs ys+-- sameLength _ _ = False+-- @+--+-- Using @'Data.List.genericLength'@, on the other hand, the laziness of+-- @'Nat'@ will indeed short-circuit:+--+-- >>> genericLength [1,2,3] == genericLength [1..]+-- False+module Numeric.Peano where++import           Data.List                   (unfoldr)++import           Control.DeepSeq             (NFData (rnf))++import           Data.Data                   (Data, Typeable)+import           GHC.Generics                (Generic)++import           Numeric.Natural++import           Data.Ix++import           Data.Function+import           Text.Read++-- $setup+-- >>> import Test.QuickCheck+-- >>> import Data.List (genericLength)+-- >>> default (Nat)+-- >>> :{+-- instance Arbitrary Nat where+--     arbitrary = fmap (fromInteger . getNonNegative) arbitrary+-- :}++-- | Peano numbers. Care is taken to make operations as lazy as+-- possible:+--+-- >>> 1 > S (S undefined)+-- False+-- >>> Z > undefined+-- False+-- >>> 3 + undefined >= 3+-- True+data Nat+    = Z+    | S Nat+    deriving (Eq,Generic,Data,Typeable)++foldrNat :: (a -> a) -> a -> Nat -> a+foldrNat f k = go+  where+    go Z     = k+    go (S n) = f (go n)+{-# INLINE foldrNat #-}++foldlNat :: (a -> a) -> a -> Nat -> a+foldlNat f = go+  where+    go !b Z = b+    go !b (S n) = go (f b) n+{-# INLINE foldlNat #-}++-- | As lazy as possible+instance Ord Nat where+    compare Z Z         = EQ+    compare (S n) (S m) = compare n m+    compare Z (S _)     = LT+    compare (S _) Z     = GT++    Z   <= _   = True+    S _ <= Z   = False+    S n <= S m = n <= m++    _ < Z = False+    n < S m = n <= m++    (>=) = flip (<=)+    (>) = flip (<)++    min Z _ = Z+    min _ Z = Z+    min (S n) (S m) = S (min n m)++    max Z m = m+    max n Z = n+    max (S n) (S m) = S (max n m)++-- | Subtraction stops at zero.+--+-- prop> n >= m ==> m - n == Z+instance Num Nat where+    n + m = foldrNat S m n+    n * m = foldrNat (m+) Z n+    abs = id+    signum Z = Z+    signum (S _) = S Z+    fromInteger n+        | n < 0 = error "cannot convert negative integers to Peano numbers"+        | otherwise = go n where+            go 0 = Z+            go m = S (go (m-1))+    n   - Z   = n+    S n - S m = n - m+    Z   - S _ = Z++-- | The maximum bound here is infinity.+--+-- prop> maxBound > (n :: Nat)+instance Bounded Nat where+    minBound = Z+    maxBound = fix S++-- | Shows integer representation.+instance Show Nat where+    showsPrec n = showsPrec n . toInteger++-- | Reads the integer representation.+instance Read Nat where+    readPrec = fmap (fromIntegral :: Natural -> Nat) readPrec++-- | Will obviously diverge for values like `maxBound`.+instance NFData Nat where+    rnf Z     = ()+    rnf (S n) = rnf n++instance Real Nat where+    toRational = toRational . toInteger++-- | Uses custom 'enumFrom', 'enumFromThen', 'enumFromThenTo' to avoid+-- expensive conversions to and from 'Int'.+--+-- >>> [1..3]+-- [1,2,3]+-- >>> [1..1]+-- [1]+-- >>> [2..1]+-- []+-- >>> take 3 [1,2..]+-- [1,2,3]+-- >>> take 3 [5,4..]+-- [5,4,3]+-- >>> [1,3..7]+-- [1,3,5,7]+-- >>> [5,4..1]+-- [5,4,3,2,1]+-- >>> [5,3..1]+-- [5,3,1]+instance Enum Nat where+    succ = S+    pred (S n) = n+    pred Z = error "pred called on zero nat"+    fromEnum = foldlNat succ 0+    toEnum m+      | m < 0 = error "cannot convert negative number to Peano"+      | otherwise = go m+      where+        go 0 = Z+        go n = S (go (n - 1))+    enumFrom = iterate S+    enumFromTo n m = unfoldr f (n, S m - n)+      where+        f (_,Z) = Nothing+        f (e,S l) = Just (e, (S e, l))+    enumFromThen n m = iterate t n+      where+        ts Z mm = (+) mm+        ts (S nn) (S mm) = ts nn mm+        ts nn Z = subtract nn+        t = ts n m+    enumFromThenTo n m t = unfoldr f (n, jm)+      where+        ts (S nn) (S mm) = ts nn mm+        ts Z mm = (S t - n, (+) mm, mm)+        ts nn Z = (S n - t, subtract nn, nn)+        (jm,tf,tt) = ts n m+        td = subtract tt+        f (_,Z) = Nothing+        f (e,l@(S _)) = Just (e, (tf e, td l))+++-- | Errors on zero.+--+-- >>> 5 `div` 2+-- 2+instance Integral Nat where+    toInteger = foldlNat succ 0+    quotRem _ Z = (maxBound, error "divide by zero")+    quotRem x y = qr Z x y+      where+        qr q n m = go n m+          where+            go nn Z          = qr (S q) nn m+            go (S nn) (S mm) = go nn mm+            go Z (S _)       = (q, n)+    quot n m = go n where+      go = subt m where+        subt Z nn          = S (go nn)+        subt (S mm) (S nn) = subt mm nn+        subt (S _) Z       = Z+    rem _ Z = error "divide by zero"+    rem nn mm = r nn mm where+      r n m = go n m where+        go nnn Z           = r nnn m+        go (S nnn) (S mmm) = go nnn mmm+        go Z (S _)         = n+    div = quot+    mod = rem+    divMod = quotRem++instance Ix Nat where+    range = uncurry enumFromTo+    inRange = uncurry go where+      go (S _) _ Z         = False+      go Z y x             = x <= y+      go (S x) (S y) (S z) = go x y z+      go (S _) Z (S _)     = False+    index = uncurry go where+      go Z h i             = lim 0 h i+      go (S _) _ Z         = error "out of range"+      go (S l) (S h) (S i) = go l h i+      go (S _) Z (S _)     = error "out of range"+      lim _ Z (S _)      = error "out of range"+      lim !a (S n) (S m) = lim (a + 1) n m+      lim !a _ Z         = a
+ src/Numeric/Peano/Typelevel.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DataKinds            #-}+{-# LANGUAGE KindSignatures       #-}+{-# LANGUAGE NoStarIsType         #-}+{-# LANGUAGE TypeFamilies         #-}+{-# LANGUAGE TypeOperators        #-}+{-# LANGUAGE UndecidableInstances #-}++{-# OPTIONS_GHC -fno-warn-unticked-promoted-constructors #-}++module Numeric.Peano.Typelevel where++import           GHC.TypeLits  (ErrorMessage (..), TypeError)+import qualified GHC.TypeLits  as Lit+import           Numeric.Peano++type family (==) (n :: Nat) (m :: Nat) :: Bool where+    Z == Z = True+    S n == S m = n == m+    Z == S _ = False+    S _ == Z = False++type family FromLit (n :: Lit.Nat) :: Nat where+    FromLit 0 = Z+    FromLit n = FromLit2 (Lit.Mod n 2) (FromLit (Lit.Div n 2))++type family FromLit2 (odd :: Lit.Nat) (rest :: Nat) :: Nat where+    FromLit2 0 n = n+    FromLit2 1 n = S n++type family ToLit (n :: Nat) :: Lit.Nat where+    ToLit Z = 0+    ToLit (S n) = 1 Lit.+ (ToLit n)++type family Compare (n :: Nat) (m :: Nat) :: Ordering where+    Compare Z Z         = EQ+    Compare (S n) (S m) = Compare n m+    Compare Z (S _)     = LT+    Compare (S _) Z     = GT++type family (<=) (n :: Nat) (m :: Nat) :: Bool where+    Z   <= _   = True+    S _ <= Z   = False+    S n <= S m = n <= m++type family (<) (n :: Nat) (m :: Nat) :: Bool where+    _ < Z = False+    n < S m = n <= m++type family Min (n :: Nat) (m :: Nat) :: Nat where+    Min Z _ = Z+    Min _ Z = Z+    Min (S n) (S m) = S (Min n m)++type family Max (n :: Nat) (m :: Nat) :: Nat where+    Max Z m = m+    Max n Z = n+    Max (S n) (S m) = S (Max n m)++infixl 6 ++type family (+) (n :: Nat) (m :: Nat) :: Nat where+    Z + m = m+    S n + m = S (n + m)++infixl 7 *+type family (*) (n :: Nat) (m :: Nat) :: Nat where+    Z   * _ = Z+    S n * m = m + n * m++infixl 6 -+type family (-) (n :: Nat) (m :: Nat) :: Nat where+    n   - Z   = n+    S n - S m = n - m+    Z   - S _ = Z++type family (%) (n :: Nat) (m :: Nat) :: Nat where+    _ % Z = TypeError (Text "divide by zero")+    n % m = Rem' n m n m++type family Rem' (n :: Nat) (m :: Nat) (n' :: Nat) (m' :: Nat) :: Nat where+    Rem' n m n' Z = Rem' n' m n' m+    Rem' n m (S n') (S m') = Rem' n m n' m'+    Rem' n m Z (S _) = n++type family (/) (n :: Nat) (m :: Nat) :: Nat where+    _ / Z = TypeError (Text "divide by zero")+    n / m = Div' m n m++type family Div' (m :: Nat) (n' :: Nat) (m' :: Nat) :: Nat where+    Div' m n' Z = S (Div' m n' m)+    Div' m (S n') (S m') = Div' m n' m'+    Div' m Z (S _) = Z
+ test/Spec.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DataKinds           #-}+{-# LANGUAGE RankNTypes          #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell     #-}+{-# LANGUAGE TypeApplications    #-}++import           Hedgehog+import qualified Hedgehog.Gen        as Gen+import qualified Hedgehog.Range      as Range++import           Numeric.Peano++import           Control.Monad+import           Data.Ix++binaryProp+    :: forall a.+       Integral a+    => (forall t. Integral t => t -> t -> t)+    -> Integer+    -> Integer+    -> (Integer -> Integer -> Bool)+    -> Property+binaryProp op lb ub cond = property $ do+    x <- forAll (Gen.integral (Range.linear lb ub))+    y <- forAll (Gen.integral (Range.linear lb ub))+    guard (cond x y)+    let zb = op x y+    let zt = op (fromInteger @a x) (fromInteger y)+    zb === toInteger zt++holdsForLength :: Foldable f => (a -> Bool) -> f a -> Int+holdsForLength p = flip (foldr f id) 0 where+  f e a i | p e = a (i + 1)+          | otherwise = i++enumProps+    :: forall a.+       (Enum a, Show a, Ord a)+    => (Int -> Bool) -> Gen Int -> Gen a -> Property+enumProps p ig eg = property $ do+    x <- forAll ig+    annotate "from . to"+    (fromEnum . toEnum @a) x === x+    annotate "to . from"+    n <- forAll eg+    (toEnum . fromEnum) n === n+    annotate "[n..]"+    let lhs1 = take 100 $ map fromEnum [n..]+        rhs1 = take 100 [fromEnum n..]+        len1 = min (holdsForLength p lhs1) (holdsForLength p rhs1)+    take len1 lhs1 === take len1 rhs1+    annotate "[n,m..]"+    m <- forAll eg+    let lhs2 = take 100 $ map fromEnum [n,m..]+        rhs2 = take 100 [fromEnum n, fromEnum m..]+        len2 = min (holdsForLength p lhs2) (holdsForLength p rhs2)+    take len2 lhs2 === take len2 rhs2+    when (m >= n) $ do+        annotate "[n..m]"+        map fromEnum [n..m] === [fromEnum n..fromEnum m]+    l <- forAll eg+    when (((l > n) == (n > m)) && (l /= n)) $ do+        annotate "[l,n..m]"+        map fromEnum [l,n..m] === [fromEnum l, fromEnum n..fromEnum m]+++prop_PeanoAdd :: Property+prop_PeanoAdd = binaryProp @Nat (+) 0 1000 (\_ _ -> True)++prop_PeanoMul :: Property+prop_PeanoMul = binaryProp @Nat (*) 0 1000 (\_ _ -> True)++prop_PeanoSub :: Property+prop_PeanoSub = withDiscards 1000 $ binaryProp @Nat (-) 0 1000 (>=)++prop_PeanoRem :: Property+prop_PeanoRem = binaryProp @Nat rem 0 1000 (\_ y -> y > 0)++prop_PeanoQuot :: Property+prop_PeanoQuot = binaryProp @Nat quot 0 1000 (\_ y -> y > 0)++-- prop_PeanoOrd :: Property+-- prop_PeanoOrd = property $ ord (Gen.integral (Range.linear @Nat 0 1000))(\n -> Gen.integral (Range.linear n (n+5)))++prop_PeanoEnum :: Property+prop_PeanoEnum =+    enumProps+        (>= 0)+        (Gen.integral (Range.linear 0 1000))+        (Gen.integral (Range.linear @Nat 0 1000))++prop_PeanoInRange :: Property+prop_PeanoInRange = property $ do+    l <- forAll (Gen.integral (Range.linear Z 100))+    u <- forAll (Gen.integral (Range.linear l (l+100)))+    i <- forAll (Gen.integral (Range.linear 0 300))+    inRange (l,u) i === (l <= i &&  i <= u)++prop_PeanoIndex :: Property+prop_PeanoIndex = property $ do+    l <- forAll (Gen.integral (Range.linear Z 100))+    u <- forAll (Gen.integral (Range.linear l (l+100)))+    i <- forAll (Gen.integral (Range.linear l u))+    unless (inRange (l,u) i) discard+    index (l,u) i === fromEnum (i - l)++main :: IO Bool+main = checkParallel $$(discover)