toIntegralSized

Programming involves a really upsetting amount of numbers. We are perpetually finding ourselves dealing with numbers in the sorts of programs that seemingly have no just cause to expose us to arithmetic tedium. The greatest redeeming quality of numbers is the sense of possibility conveyed by the unlimited expanse of the number line – But machines mute even this glimmer of hope, offering instead a dim and claustrophobic view.

Here’s the thing: We treat our numbers in a most cruel manner, packing them into dark corners, shoving them through nanoscopically thin wires. They live in tiny boxes, because in computing, small is fast. And under these conditions, our numbers cannot handle much stress before they start bouncing off the walls.

Try starting with an Int64 of 100 and repeatedly square it. You won’t get far.See Introduction to GHCi for a discussion of it.

λ> import Data.Int

λ> 100 :: Int64
100

λ> it ^ 2
10000

λ> it ^ 2
100000000

λ> it ^ 2
10000000000000000

λ> it ^ 2
-8814407033341083648

Int64 is not a type for unbounded possibilities, not a realm in which to explore the vast glory of mathematics. Int64 offers efficient and uninspiring service for tasks such as… counting.

λ> 1 :: Int64
1

λ> it + 1
2

λ> it + 1
3

λ> it + 1
4

λ> it + 1
5

For this sort of thing, Int64 will pretty much stay in its lane. It takes a while to plus-one your way out of an Int64 box.

We do have an unbounded type! It’s called Integer.Mnemonic: The type with the longer name can hold bigger numbers. Just as ‘Int’ is an abbrevation for ‘Integer’, types with ‘Int’ in their names are “abbreviated” notions of integerness. The Integer type will neither give you up nor let you down.

λ> 100 :: Integer
100

λ> it ^ 2
10000

λ> it ^ 2
100000000

λ> it ^ 2
10000000000000000

λ> it ^ 2
10000000000000000000000000
0000000

λ> it ^ 2
10000000000000000000000000
00000000000000000000000000
0000000000000

λ> it ^ 2
10000000000000000000000000
00000000000000000000000000
00000000000000000000000000
00000000000000000000000000
0000000000000000000000000

It’s so nice, it makes you want to move to Integer Land, buy a little house there, plant some fruit trees, spend your evenings on the porch with a glass of wine looking out over the –

Snap out of it, you’re daydreaming! You’re not going to get Integers all the time, it’s just not realistic. In fact, it’s only going to get worse.

Do you know what the upper bound on Int64 is? We said it was small, but that’s only in comparison to infinity. Really, it’s kind of an absurdly high number.

λ> maxBound :: Int64
9223372036854775807

A lot of situations that call for a number are situations where we know that the number won’t be nearly that high. Say you’ve got a cheap digital room thermometer that reports temperatures in whole degrees Fahrenheit. Maybe it’s going to give you an Int16. That’s way smaller than Int64, but this bad boy can still fit so many temperatures.

λ> minBound :: Int16
-32768

λ> maxBound :: Int16
32767

Programmers, hardware builders, network protocol designers – they’re all going to give you numbers, and they’re all going to require numbers from you. And here’s the snag: Because everybody is putting their numbers into the smallest box they can get away with, and because how small a box you can get away with depends on exactly what you’re doing, sooner or later you’re going to end up with a square peg and a round hole.

Shot from “Apollo 13” (1995). The central figure is holding a cube in one hand and a cylinder in the other.“We gotta find a way to make this fit into the hole for this” – from Apollo 13 (1995)

It is astonishing how casually we often see numbers abused as they are shoved from one box to another. The fromIntegral function in the Prelude module is one example of how numbers can get hurt.fromIntegral

fromIntegral :: (Integral a, Num b) => a -> b

This is an extremely polymorphic function – Both its input and output are type variables. So we’re going to need some type annotations in our demonstration, to ensure that the compiler knows what types we’re converting to and from.

Here’s how it works on a good day:

λ> fromIntegral (1243 :: Int64) :: Int16
1243

A number goes in, the same number comes out in new clothes as a different type. But what happens when the number doesn’t fit into its new clothes? Nothing good, I’ll tell you.

λ> fromIntegral (475891 :: Int64) :: Int16
17139

λ> fromIntegral (-1243 :: Int64) :: Word64
18446744073709550373

Don’t treat your numbers like this!

Generally the best tool for this kind of work is a function called toIntegralSized.

toIntegralSized ::
    ( Integral a, Bits a,
      Integral b, Bits b ) =>
    a -> Maybe b

This function comes from a rather obscure place at the bottom of the Data.BitsData.Bits module in the base package. You can alsoRelude.Numeric find it in the relude package, where it is included in the Relude module and featured more prominently in the Relude.Numeric module.

λ> toIntegralSized (1243 :: Int64) :: Maybe Int16
Just 1243

λ> toIntegralSized (475891 :: Int64) :: Maybe Int16
Nothing

The output type is Maybe because a reasonable conversion from one type of number to another is not always possible, owing to each type’s different boundaries. As the example above illustrates, if an Int64 value is too large, an attempt to transfer it into a smaller Int16 box may yield Nothing.

Likewise, a negative integer will fail to convert to any “word” type; such things are only for non-negative numbers.Data.Word

λ> import Data.Word

λ> toIntegralSized (1243 :: Int64) :: Maybe Word64
Just 1243

λ> toIntegralSized (-1243 :: Int64) :: Maybe Word64
Nothing

With toIntegralSized at your disposal, you can confidently tackle those pesky integer conversion tasks and shift your numbers around into whatever format is required of them. It doesn’t remove all your need to think, because you still have that Nothing possibility to deal with, but you have an extremely versatile conversion function that never produces erroneous off-the-wall results.

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.