Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a practical way of using natural numbers in Haskell?

I'm learning Haskell and would like to impose the use of positive integers (1,2,3, ...) in some constructors, but I only seem to find the 'Int' and 'Integer' datatypes.

I could use the canonical

data Nat = Zero | Succ Nat 

but then I couldn't use 1, 4, ... to denote them.

So I ask, is there a way to accomplish this? (which is like using 'unsigned' in C)

Thanks in advance.

EDIT: I'm going the way of hiding it inside a module, as explained by C. A. McCann. Also, I must add the following link: http://haskell.org/haskellwiki/Smart_constructors for a summary on the subject. Thanks for taking the time to answer!

like image 685
Seymour Kooze Avatar asked Jul 30 '11 15:07

Seymour Kooze


People also ask

How do you define natural numbers in Haskell?

The “natural” numbers, The Natural type in Haskell does include zero. which include just the positive whole numbers. The “rational” numbers, which includes all the whole numbers like integers, but also all the fractional numbers.

What does == mean in Haskell?

The == is an operator for comparing if two things are equal. It is quite normal haskell function with type "Eq a => a -> a -> Bool". The type tells that it works on every type of a value that implements Eq typeclass, so it is kind of overloaded.

What does NUM mean in Haskell?

Num is a typeclass — a group of types — which includes all types which are regarded as numbers. The (Num a) => part of the signature restricts a to number types – or, in Haskell terminology, instances of Num .

What are natural numbers used for?

The natural numbers are used for three main purposes: for counting, for ordering, and for defining other concepts. Counting is the natural way to measure the quantity of a set of several discrete, individually identifiable objects.


2 Answers

There's generally two approaches for this: The inductive definition you gave, or an abstract data type using something else for internal representation.

Note that the inductive representation is not terribly efficient for large numbers; however, it can be lazy, which lets you do things like see which of two nats is larger without evaluating further than the size of the smaller one.

An abstract data type is one which is defined in a separate module and does not export its constructors, examples being IO or Data.Set.Set. You can define something like this:

module Nat (Nat() {- etc. -} ) where  newtype Nat = Nat { unNat :: Integer } 

...where you export various operations on Nat such that, even though the internal representation is just Integer, you ensure that no value of type Nat is constructed holding a negative value.

In both cases, if you want to use numeric literals, you'll need a definition of fromInteger, which is attached to the Num type class, which is completely wrong for natural numbers but oh well.

If you don't mind making a broken instance just to get syntactic niceties, you can do something like this:

instance Num Nat where     Zero + n = n     n + Zero = n     (Succ n1) + (Succ n2) = Succ . Succ $ n1 + n2      fromInteger 0 = Zero     fromInteger i | i > 0 = Succ . fromInteger $ i - 1 

...and so on, for the other functions. The same can be done for the abstract data type approach, just be careful to not use deriving to get an automatic Num instance, because it will happily break your non-negative constraint.

like image 162
C. A. McCann Avatar answered Sep 18 '22 19:09

C. A. McCann


You can use Word32 from Data.Word, which corresponds to uint32_t in C.

With Word32 you get the same problems as with the unsigned types in C, especially over- and underflow. If you want to make sure that doesn't happen, you'd need to wrap it to a newtype and only export a smart constructor. Thus no addition, subtraction etc. would be possible and there's no risk of over- or underflow. If you wanted to support addition, for example, you could add and export a function for adding unsigned ints, but with a check for overflow (and with a performance penalty). It could then look like this:

module NT(UInt, addUInts) where  import Data.Word  newtype UInt = UInt Word32   deriving (Show)  mkUInt :: Word32 -> UInt mkUInt = UInt  addUInts :: UInt -> UInt -> Maybe UInt addUInts (UInt u1) (UInt u2) =   let u64 :: Word64       u64 = fromIntegral u1 + fromIntegral u2   in if u64 > fromIntegral (maxBound :: Word32)        then Nothing        else Just (UInt (fromIntegral u64)) 
like image 31
Antti Avatar answered Sep 21 '22 19:09

Antti