patternMinor
Integers from scratch in Haskell
Viewed 0 times
fromintegershaskellscratch
Problem
Inspired by this article about natural numbers from first principles in swift I implemented integers from scratch in Haskell.
Besides obviously being extremely inefficient, is this code idiomatic Haskell?
```
import Data.List
import GHC.Real
data ℤ = Pred ℤ | Zero | Succ ℤ
toNat = toEnum :: Int -> ℤ
fromNat = fromEnum :: ℤ -> Int
toInteger' :: ℤ -> Integer
toInteger' Zero = 0
toInteger' (Succ n) = toInteger' n + 1
toInteger' (Pred n) = toInteger' n - 1
instance Show ℤ where
show = ((++) "Nat: ") . show . toInteger'
data SimpleNat = MinusOne | One deriving (Eq, Ord, Show)
toList :: ℤ -> [SimpleNat]
toList Zero = []
toList (Succ n) = One : toList n
toList (Pred n) = MinusOne : toList n
fromList :: [SimpleNat] -> ℤ
fromList [] = Zero
fromList (One:xs) = Succ (fromList xs)
fromList (MinusOne:xs) = Pred (fromList xs)
normaliseList :: [SimpleNat] -> [SimpleNat]
normaliseList xs = normaliseSorted $ sort xs
where normaliseSorted xs = map fst filtered
filtered = filter (uncurry (==)) $ zip xs (reverse xs)
normalise :: ℤ -> ℤ
normalise = fromList . normaliseList . toList
instance Enum ℤ where
succ (Pred n) = n
succ n = Succ n
pred (Succ n) = n
pred n = Pred n
toEnum n | n 0 = Succ $ toEnum (n-1)
| otherwise = Zero
fromEnum Zero = 0
fromEnum (Succ n) = fromEnum n + 1
fromEnum (Pred n) = fromEnum n - 1
instance Num ℤ where
Zero + n = n
n + Zero = n
(Succ n) + m = Succ (n + m)
(Pred n) + m = Pred (n + m)
abs n = abs' $ normalise n
where abs' (Pred n) = Succ (abs' n)
abs' n = n
signum n = signum' $ normalise n
where signum' Zero = Zero
signum' (Succ n) = Succ Zero
signum' (Pred n) = Pred Zero
negate n = negate' $ normalise n
where negate' Zero = Zero
negate' (Succ n) = Pred (negate' n)
negate' (Pred n) = Succ (negate' n)
fromInteger n | n 0
Besides obviously being extremely inefficient, is this code idiomatic Haskell?
```
import Data.List
import GHC.Real
data ℤ = Pred ℤ | Zero | Succ ℤ
toNat = toEnum :: Int -> ℤ
fromNat = fromEnum :: ℤ -> Int
toInteger' :: ℤ -> Integer
toInteger' Zero = 0
toInteger' (Succ n) = toInteger' n + 1
toInteger' (Pred n) = toInteger' n - 1
instance Show ℤ where
show = ((++) "Nat: ") . show . toInteger'
data SimpleNat = MinusOne | One deriving (Eq, Ord, Show)
toList :: ℤ -> [SimpleNat]
toList Zero = []
toList (Succ n) = One : toList n
toList (Pred n) = MinusOne : toList n
fromList :: [SimpleNat] -> ℤ
fromList [] = Zero
fromList (One:xs) = Succ (fromList xs)
fromList (MinusOne:xs) = Pred (fromList xs)
normaliseList :: [SimpleNat] -> [SimpleNat]
normaliseList xs = normaliseSorted $ sort xs
where normaliseSorted xs = map fst filtered
filtered = filter (uncurry (==)) $ zip xs (reverse xs)
normalise :: ℤ -> ℤ
normalise = fromList . normaliseList . toList
instance Enum ℤ where
succ (Pred n) = n
succ n = Succ n
pred (Succ n) = n
pred n = Pred n
toEnum n | n 0 = Succ $ toEnum (n-1)
| otherwise = Zero
fromEnum Zero = 0
fromEnum (Succ n) = fromEnum n + 1
fromEnum (Pred n) = fromEnum n - 1
instance Num ℤ where
Zero + n = n
n + Zero = n
(Succ n) + m = Succ (n + m)
(Pred n) + m = Pred (n + m)
abs n = abs' $ normalise n
where abs' (Pred n) = Succ (abs' n)
abs' n = n
signum n = signum' $ normalise n
where signum' Zero = Zero
signum' (Succ n) = Succ Zero
signum' (Pred n) = Pred Zero
negate n = negate' $ normalise n
where negate' Zero = Zero
negate' (Succ n) = Pred (negate' n)
negate' (Pred n) = Succ (negate' n)
fromInteger n | n 0
Solution
This is some good looking code, there are just a few things that throw me. The first is that you're defining the integers but occasionally referring to them as "Nat"s. The natural numbers are non-negative integers, don't confuse the two.
This is a strange construction, give the type signature before the definition of the function and GHC will figure out which version of
Your
Your normalization function is more complex than it needs to be, but consider what having to normalize says about the representation you picked. Here's one version that doesn't change anything about your data types.
You have some unnecessary pattern match cases, such as
Here is an alternate construction that could obviate the need for all of the expensive normalization your version incurs. Writing the
toNat = toEnum :: Int -> ℤ
fromNat = fromEnum :: ℤ -> IntThis is a strange construction, give the type signature before the definition of the function and GHC will figure out which version of
toEnum and fromEnum to use. Anything else is unusual and unnecessary.Your
Show instance should really just punt to the instance for Integers. Again, naturals numbers aren't integers so your "Nat: " tag is incorrect, it doesn't really add anything, and it makes writing a Read instance more difficult. Also there's no need to section infix functions by writing them prefix style. Thus:instance Show ℤ where
show = show . toInteger
instance Read ℤ where
read = fromInteger . readYour normalization function is more complex than it needs to be, but consider what having to normalize says about the representation you picked. Here's one version that doesn't change anything about your data types.
normalise = fromList . uncurry tailOfLonger . partition isOne . toList
where
isOne :: SimpleNat -> Bool
isOne One = True
isOne _ = False
tailOfLonger :: [a] -> [a] -> [a]
tailOfLonger [] ys = ys
tailOfLonger xs [] = xs
tailOfLonger (_:xs) (_:ys) = tailOfLonger xs ysYou have some unnecessary pattern match cases, such as
n + Zero = n in the Num instance declaration. There's nothing wrong operationally with that case of course, but it's superfluous and the beauty of the inductive construction of the integers encourages me at least to be ruthless with flensing redundant code.Here is an alternate construction that could obviate the need for all of the expensive normalization your version incurs. Writing the
Num instance is pretty fun.data Nat = Zero | Succ Nat
data Z = Negative Nat | NonNegative NatCode Snippets
toNat = toEnum :: Int -> ℤ
fromNat = fromEnum :: ℤ -> Intinstance Show ℤ where
show = show . toInteger
instance Read ℤ where
read = fromInteger . readnormalise = fromList . uncurry tailOfLonger . partition isOne . toList
where
isOne :: SimpleNat -> Bool
isOne One = True
isOne _ = False
tailOfLonger :: [a] -> [a] -> [a]
tailOfLonger [] ys = ys
tailOfLonger xs [] = xs
tailOfLonger (_:xs) (_:ys) = tailOfLonger xs ysdata Nat = Zero | Succ Nat
data Z = Negative Nat | NonNegative NatContext
StackExchange Code Review Q#79761, answer score: 3
Revisions (0)
No revisions yet.