numeric-kinds-0.2.0: src/Kinds/Integer.hs
-- Copyright 2020-2021 Google LLC
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- | Type-level 'Integer'.
--
-- Import Integer qualified as K and refer to it as K.Integer.
--
-- It turns out this is approximately identical to an in-progress GHC change
-- https://github.com/ghc-proposals/ghc-proposals/pull/154, right down to the
-- particulars of how to encode kind-classes. Apparently I was on the right
-- track! TODO(awpr): when that thing is merged and readily available, remove
-- this or turn it into a compat package.
--
-- Once that proposal is complete, we can migrate by replacing K.Integer with
-- just Integer.
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module Kinds.Integer
( -- * Type-level Integers
Integer(..), ToInteger
-- ** Runtime Values
, KnownInteger(..)
-- ** Specialized Arithmetic and Comparison
, AddInteger, SubInteger, CmpInteger, MulInteger
, type (-#)
-- * Axioms
, plusMinusInverseL, plusMinusInverseR
, mulCommutes
) where
import Prelude hiding (Integer)
import qualified Prelude as P
import Data.Type.Equality ((:~:)(..))
import GHC.Exts (proxy#)
import GHC.TypeNats (KnownNat, Nat, natVal')
import Unsafe.Coerce (unsafeCoerce)
import Kinds.Num (type (+), type (-), type (*), FromNat, ToInteger)
import Kinds.Ord (Compare, OrdCond)
-- | Type-level signed numbers
data Integer = Pos Nat | Neg Nat
-- | Mostly internal; the "snumber" package provides more useful functionality.
class KnownInteger (n :: Integer) where
integerVal :: P.Integer
instance KnownNat n => KnownInteger ('Pos n) where
integerVal = toInteger $ natVal' @n proxy#
instance KnownNat n => KnownInteger ('Neg n) where
integerVal =
let x = natVal' @n proxy#
in if x == 0
then error "illegal KnownInteger (-0)"
else negate $ toInteger x
type instance Compare {- k=Integer -} x y = CmpInteger x y
type instance FromNat {- k=Integer -} n = 'Pos n
type instance ToInteger {- k=Integer -} n = n
type instance x + y = AddInteger x y
type instance x - y = SubInteger x y
type instance x * y = MulInteger x y
-- | Comparison of type-level integers.
type family CmpInteger m n where
CmpInteger ('Pos m) ('Pos n) = Compare m n
CmpInteger ('Pos 0) ('Neg 0) = 'EQ
CmpInteger ('Pos m) ('Neg n) = 'GT
CmpInteger ('Neg 0) ('Pos 0) = 'EQ
CmpInteger ('Neg m) ('Pos n) = 'LT
CmpInteger ('Neg m) ('Neg n) = Compare n m -- Note: reversed
-- | Given two 'Nat's @m@ and @n@, computes @m - n@ as an 'Integer'.
type family (m :: Nat) -# (n :: Nat) where
-- Redundant cases solely to make sure we get stuck reducing '-#' rather
-- than reducing @OrdCond (Compare m n) tons of junk@ when the magnitude is
-- a type variable. This is similar to adding a bang pattern or case
-- statement to a term-level function to make it strict in its arguments; but
-- we don't have case statements or bang patterns in type families, so we add
-- clauses instead.
n -# 0 = 'Pos n -- Order matters: 0 -# 0 should be 'Pos 0
0 -# n = 'Neg n
m -# n = OrdCond (Compare m n)
('Neg (n - m))
('Pos 0)
('Pos (m - n))
-- | Addition of type-level integers.
type family AddInteger m n where
AddInteger ('Pos m) ('Pos n) = 'Pos (m + n)
AddInteger ('Pos m) ('Neg n) = m -# n
AddInteger ('Neg m) ('Pos n) = n -# m
AddInteger ('Neg m) ('Neg n) = 'Neg (m + n)
-- | Subtraction of type-level integers.
type family SubInteger m n where
-- Note we could define this as @AddInteger m (Negate n)@, but then GHC would
-- reduce eagerly to this when the parameters are type variables, and
-- irreducible calls to 'SubInteger' would appear in error messages as
-- @AddInteger m (Negate n)@ rather than the more readable @SubInteger m n@.
-- So, we tolerate some duplicated code in order to make sure we get stuck on
-- 'SubInteger' rather than later.
SubInteger ('Pos m) ('Pos n) = m -# n
SubInteger ('Pos m) ('Neg n) = 'Pos (m + n)
SubInteger ('Neg m) ('Pos n) = 'Neg (m + n)
SubInteger ('Neg m) ('Neg n) = n -# m
-- | Multiplication of type-level integers.
type family MulInteger m n where
MulInteger ('Pos m) ('Pos n) = 'Pos (m * n)
MulInteger ('Pos m) ('Neg n) = 0 -# (m * n)
MulInteger ('Neg m) ('Pos n) = 0 -# (m * n)
MulInteger ('Neg m) ('Neg n) = 'Pos (m * n)
unsafeAxiom :: a :~: b
unsafeAxiom = unsafeCoerce Refl
-- | Subtracting the right summand gives back the left summand.
plusMinusInverseR :: (m + n) -# n :~: 'Pos m
plusMinusInverseR = unsafeAxiom
-- | Subtracting the left summand gives back the right summand.
plusMinusInverseL :: (m + n) -# m :~: 'Pos n
plusMinusInverseL = unsafeAxiom
-- | Multiplication of integers is commutative.
mulCommutes :: MulInteger m n :~: MulInteger n m
mulCommutes = unsafeAxiom