I have recently discovered that in type theory there is a concept of a "predicate type" which is a type \$A\$ formed out of all members of the underlying type \$U\$ that satisfy a given predicate function pred :: u -> Bool
. I wondered if we can do that in Haskell, so I took this library for primes and decided to make a type for the prime numbers. This is how it looks:
module Data.Numbers.Primes.Type
(Prime
, getValue
, getIndex
, primeIndex
, getPrime
, maybePrime
) where
import Data.List (elemIndex)
import Data.Numbers.Primes
data Prime int = Prime { getValue :: int, getIndex :: Int } deriving Show
instance Integral int => Enum (Prime int) where
toEnum = getPrime
fromEnum = getIndex
instance Eq (Prime int) where
x == y = getIndex x == getIndex y
instance Ord (Prime int) where
x `compare` y = getIndex x `compare` getIndex y
-- | If a given number is prime, give its index.
primeIndex :: (Integral n, Integral i) => n -> Maybe i
primeIndex x | isPrime x = fromIntegral <$> elemIndex x primes
| otherwise = Nothing
-- | Give n-th prime.
getPrime :: (Integral n, Integral int) => n -> Prime int
getPrime n = Prime (primes !! fromIntegral n) (fromIntegral n)
-- | If a given number is prime, give it back wrapped as such.
maybePrime :: (Integral n, Integral int) => n -> Maybe (Prime int)
maybePrime x | isPrime x = Prime (fromIntegral x) <$> primeIndex x
| otherwise = Nothing
I'm certain this code is flawed in many ways yet unbeknownst to me, of which I hope the condescending reader would let me know so I could improve. However, there are a few points that I'm suspicious about even now:
- Am I dealing with index type the right way?
- Is it good to "cache" a prime's index inside the data object?
- Am I doing it right?
- Would it be possible and/or better to cache the indices implicitly via a memoizing function? On the pros side I see one less explicit data field which consistency could be compromised, on the contras side that we will not be able to easily save and restore data objects inbetween program runs.
- Is an
Int
the right type for storing indices of primes, in the light of the facts that no prime has a negative index and there is a typeWord
for bounded unsigned integer values? (For some reason, Prelude makes no use ofWord
for, say, functions that deal with list indices, so, in suspicion that this design choice may have had a good motivation, I for now refrained from using theWord
either.)- Maybe the index type should rather be polymorphic over the
Integral
class?
- Maybe the index type should rather be polymorphic over the
- Is it good to use
fromIntegral
everywhere to make the types of the functions that deal with indices more general?
- Is it good to "cache" a prime's index inside the data object?
- Is this type actually safe as it's intended to be? That is, can I be sure there is no way to construct a Prime data object containing an arbitrary value or an inconsistent index? For example, if I used a
newtype
(as I did in one of the drafts) and a derivedEnum
instance, I would be able totoEnum
any number into prime. Does the design ensure this will not be the case ever anymore? (At least, in a reasonable usage scenario involving a non-malevolent user.)