{-| Copyright : (C) 2016, University of Twente License : BSD2 (see the file LICENSE) Maintainer : Christiaan Baaij Some \"magic\" classes and instances to get the "GHC.TypeLits.KnownNat.Solver" type checker plugin working. = Usage Let's say you defined a closed type family @Max@: @ import Data.Type.Bool (If) import GHC.TypeLits type family Max (a :: Nat) (b :: Nat) :: Nat where Max 0 b = b Max a b = If (a <=? b) b a @ if you then want the "GHC.TypeLits.KnownNat.Solver" to solve 'KnownNat' constraints over @Max@, given just 'KnownNat' constraints for the arguments of @Max@, then you must define: @ \{\-# LANGUAGE DataKinds, FlexibleInstances, GADTs, KindSignatures, MultiParamTypeClasses, ScopedTypeVariables, TemplateHaskell, TypeApplications, TypeFamilies, TypeOperators, UndecidableInstances \#-\} import Data.Proxy (Proxy (..)) import Data.Singletons.TH (genDefunSymbols) import GHC.TypeLits.KnownNat $(genDefunSymbols [''Max]) -- creates the \'MaxSym0\' symbol instance (KnownNat a, KnownNat b) => 'KnownNat2' $('nameToSymbol' ''Max) a b where type 'KnownNatF2' $('nameToSymbol' ''Max) = MaxSym0 natSing2 = let x = natVal (Proxy @a) y = natVal (Proxy @b) z = max x y in 'SNatKn' z \{\-# INLINE natSing2 \#-\} @ = FAQ ==== 1. "GHC.TypeLits.KnownNat.Solver" does not seem to find the corresponding 'KnownNat2' instance for my type-level operation At the Core-level, GHCs internal mini-Haskell, type families that only have a single equation are treated like type synonyms. For example, let's say we defined a closed type family @Max@: @ import Data.Type.Bool (If) import GHC.TypeLits type family Max (a :: Nat) (b :: Nat) :: Nat where Max a b = If (a <=? b) b a @ Now, a Haskell-level program might contain a constraint @ KnownNat (Max a b) @ , however, at the Core-level, this constraint is expanded to: @ KnownNat (If (a <=? b) b a) @ "GHC.TypeLits.KnownNat.Solver" never sees any reference to the @Max@ type family, so it will not look for the corresponding 'KnownNat2' instance either. To fix this, ensure that your type-level operations always have at least two equations. For @Max@ this means we have to redefine it as: @ type family Max (a :: Nat) (b :: Nat) :: Nat where Max 0 b = b Max a b = If (a <=? b) b a @ -} {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeInType #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE Trustworthy #-} {-# OPTIONS_GHC -Wno-unused-top-binds -fexpose-all-unfoldings #-} {-# OPTIONS_HADDOCK show-extensions #-} module GHC.TypeLits.KnownNat ( -- * Singleton natural number SNatKn (..) -- * Constraint-level arithmetic classes , KnownNat1 (..) , KnownNat2 (..) , KnownNat3 (..) -- * Template Haskell helper , nameToSymbol ) where import Data.Bits (shiftL) import Data.Proxy (Proxy (..)) #if MIN_VERSION_ghc(8,2,0) import GHC.TypeNats (KnownNat, Nat, type (+), type (*), type (^), type (-), type (<=), natVal) import GHC.TypeLits (Symbol) #else import GHC.TypeLits (KnownNat, Nat, Symbol, type (+), type (*), type (^), type (-), type (<=), natVal) #endif import Data.Singletons (type (~>), type (@@)) import Data.Promotion.Prelude (type (:+$), type (:*$), type (:^$), type (:-$)) #if MIN_VERSION_ghc(8,2,0) import Numeric.Natural (Natural) #endif import GHC.TypeLits.KnownNat.TH -- | Singleton natural number newtype SNatKn (n :: Nat) = #if MIN_VERSION_ghc(8,2,0) SNatKn Natural #else SNatKn Integer #endif -- | Class for arithmetic functions with /one/ argument. -- -- The 'Symbol' /f/ must correspond to the fully qualified name of the -- type-level operation. Use 'nameToSymbol' to get the fully qualified -- TH Name as a 'Symbol' class KnownNat1 (f :: Symbol) (a :: Nat) where type KnownNatF1 f :: Nat ~> Nat natSing1 :: SNatKn (KnownNatF1 f @@ a) -- | Class for arithmetic functions with /two/ arguments. -- -- The 'Symbol' /f/ must correspond to the fully qualified name of the -- type-level operation. Use 'nameToSymbol' to get the fully qualified -- TH Name as a 'Symbol' class KnownNat2 (f :: Symbol) (a :: Nat) (b :: Nat) where type KnownNatF2 f :: Nat ~> Nat ~> Nat natSing2 :: SNatKn (KnownNatF2 f @@ a @@ b) -- | Class for arithmetic functions with /three/ arguments. -- -- The 'Symbol' /f/ must correspond to the fully qualified name of the -- type-level operation. Use 'nameToSymbol' to get the fully qualified -- TH Name as a 'Symbol' class KnownNat3 (f :: Symbol) (a :: Nat) (b :: Nat) (c :: Nat) where type KnownNatF3 f :: Nat ~> Nat ~> Nat ~> Nat natSing3 :: SNatKn (KnownNatF3 f @@ a @@ b @@ c) -- | 'KnownNat2' instance for "GHC.TypeLits"' 'GHC.TypeLits.+' instance (KnownNat a, KnownNat b) => KnownNat2 $(nameToSymbol ''(+)) a b where type KnownNatF2 $(nameToSymbol ''(+)) = (:+$) natSing2 = SNatKn (natVal (Proxy @a) + natVal (Proxy @b)) {-# INLINE natSing2 #-} -- | 'KnownNat2' instance for "GHC.TypeLits"' 'GHC.TypeLits.*' instance (KnownNat a, KnownNat b) => KnownNat2 $(nameToSymbol ''(*)) a b where type KnownNatF2 $(nameToSymbol ''(*)) = (:*$) natSing2 = SNatKn (natVal (Proxy @a) * natVal (Proxy @b)) {-# INLINE natSing2 #-} -- | 'KnownNat2' instance for "GHC.TypeLits"' 'GHC.TypeLits.^' instance (KnownNat a, KnownNat b) => KnownNat2 $(nameToSymbol ''(^)) a b where type KnownNatF2 $(nameToSymbol ''(^)) = (:^$) natSing2 = let x = natVal (Proxy @ a) y = natVal (Proxy @ b) z = case x of 2 -> shiftL 1 (fromIntegral y) _ -> x ^ y in SNatKn z {-# INLINE natSing2 #-} -- | 'KnownNat2' instance for "GHC.TypeLits"' 'GHC.TypeLits.-' instance (KnownNat a, KnownNat b, b <= a) => KnownNat2 $(nameToSymbol ''(-)) a b where type KnownNatF2 $(nameToSymbol ''(-)) = (:-$) natSing2 = SNatKn (natVal (Proxy @a) - natVal (Proxy @b)) {-# INLINE natSing2 #-}