Skip to content

Instantly share code, notes, and snippets.

@CMCDragonkai
Last active February 23, 2022 23:44
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CMCDragonkai/fab0980b3325e8a788c9 to your computer and use it in GitHub Desktop.
Save CMCDragonkai/fab0980b3325e8a788c9 to your computer and use it in GitHub Desktop.
Haskell: Numeric Typeclasses - How numbers work!

Numeric Typeclasses - How numbers work!

Numbers in Haskell are typed of course. They also exist as instances of a numeric typeclass hierarchy. I was confused with converting and working with numbers of different types, and not being sure which functions were polymorphic and could work with different numeric types. So I created a little diagram. Do note that the typeclass hierarchy of Haskell actually does somewhat follow the hierarchy of numbers in Math.

The first step to realise is that all literal numbers in Haskell are constructors that construct some instance of the Num typeclass. There's a number of typeclasses that inherit from the Num typeclass, this means it's possible to type hint that a particular literal is of a particular type, such as:

> 1 :: Fractional a => a --^ literal 1 some instance in Fractional
1.0
> 1 :: RealFloat a => a --^ literal 1 is some instance in RealFloat class
1.0
> 1 :: Integer --^ Integer is a type instance part of the Integral typeclass
> 1

In GHCI, you can find out information about each type class by typing:

> :info Float
class (Num a, Ord a) => Real a where
  toRational :: a -> Rational
        -- Defined in ‘GHC.Real’
instance Real Integer -- Defined in ‘GHC.Real’
instance Real Int -- Defined in ‘GHC.Real’
instance Real Float -- Defined in ‘GHC.Float’
instance Real Double -- Defined in ‘GHC.Float’

This actually shows you the typeclass that RealFloat is encapsulated in and its possible instances.

If you have a particular type instance, and you want to know what type classes it is part of, you can also use the the :info command:

> :info Integer
data Integer
  = integer-gmp:GHC.Integer.Type.S# GHC.Prim.Int#
  | integer-gmp:GHC.Integer.Type.J# GHC.Prim.Int# GHC.Prim.ByteArray#
        -- Defined in ‘integer-gmp:GHC.Integer.Type’
instance Enum Integer -- Defined in ‘GHC.Enum’
instance Eq Integer -- Defined in ‘integer-gmp:GHC.Integer.Type’
instance Integral Integer -- Defined in ‘GHC.Real’
instance Num Integer -- Defined in ‘GHC.Num’
instance Ord Integer -- Defined in ‘integer-gmp:GHC.Integer.Type’
instance Read Integer -- Defined in ‘GHC.Read’
instance Real Integer -- Defined in ‘GHC.Real’
instance Show Integer -- Defined in ‘GHC.Show’

It's possible to add new instances and new typeclasses into the numeric type hierarchy. These are not fixed primitives. This means people have created alternative numeric preludes that adds extra functionality to the numeric system in Haskell. Check them out here:

One particular addition I like is the Data.Ratio module. This adds a Rational type alias, and the Ratio type instance:

> :info Rational
type Rational = Ratio Integer   -- Defined in ‘GHC.Real’
> :info Ratio
data Ratio a = !a GHC.Real.:% !a        -- Defined in ‘GHC.Real’
instance Integral a => Enum (Ratio a) -- Defined in ‘GHC.Real’
instance Eq a => Eq (Ratio a) -- Defined in ‘GHC.Real’
instance Integral a => Fractional (Ratio a)
  -- Defined in ‘GHC.Real’
instance Integral a => Num (Ratio a) -- Defined in ‘GHC.Real’
instance Integral a => Ord (Ratio a) -- Defined in ‘GHC.Real’
instance (Integral a, Read a) => Read (Ratio a)
  -- Defined in ‘GHC.Read’
instance Integral a => Real (Ratio a) -- Defined in ‘GHC.Real’
instance Integral a => RealFrac (Ratio a) -- Defined in ‘GHC.Real’
instance (Integral a, Show a) => Show (Ratio a)
  -- Defined in ‘GHC.Real’

Ratios allow one to write things "fractions" without evaluating them immediately. Like 1/3.

This also means that Haskell's numeric system won't always stay the same. It will evolve towards the future: http://www.reddit.com/r/haskell/comments/c9ynh/punt_the_prelude/

A note about the venn diagram

I used this https://github.com/benfred/venn.js

Plugged in this configration:

var sets = [
    {sets: ['Num'], size: 60},
    {sets: ['Ord'], size: 60},
    {sets: ['Enum'], size : 60},
    {sets: ['Fractional'], size: 20},             // Fractional is smaller than Num 
    {sets: ['Fractional', 'Num'], size: 20},      // Fractional(Num)
    {sets: ['Fractional', 'Ord'], size: 7},       // RealFrac(Real(Num + Ord) + Fractional)
    {sets: ['Fractional', 'Enum'], size: 0}, 
    {sets: ['Floating'], size: 4},                // Floating is smaller than Fractional
    {sets: ['Floating', 'Fractional'], size: 4},
    {sets: ['Floating', 'Ord'], size: 2},         // RealFloat(Floating + RealFrac(Real(Num + Ord) + Fractional))
    {sets: ['Ord', 'Num'], size: 14},             // Real(Num + Ord)
    {sets: ['Enum', 'Num'], size: 10},            // Integral(Real(Num + Ord) + Enum)
    {sets: ['Enum', 'Ord'], size: 10}
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment