Constructors

new

Based on the decisions I made in Representation I came up with the following requirements for new.

new 1 2 == Just (Rational 1 2)

new 3 0 == Nothing

new 2 4 == Just (Rational 1 2)
new 3 6 == Just (Rational 1 2)
new 4 8 == Just (Rational 1 2)

new -1 2 == Just (Rational -1 2)
new 1 -2 == Just (Rational -1 2)
new -1 -2 == Just (Rational 1 2)

new -2 4 == Just (Rational -1 2)
new 2 -4 == Just (Rational -1 2)
new -2 -4 == Just (Rational 1 2)

The following implementation satisfies the requirements:

new : Int -> Int -> Maybe Rational
new numer denom =
    if denom == 0 then
        Nothing

    else
        Just (makeRational numer denom)


makeRational : Int -> Int -> Rational
makeRational numer denom =
    let
        divisor =
            gcd numer denom

        g =
            if denom < 0 then
                -divisor

            else
                divisor

        n =
            numer // g

        d =
            denom // g
    in
    Rational n d


gcd : Int -> Int -> Int
gcd a b =
    gcdHelper (abs a) (abs b)


gcdHelper : Int -> Int -> Int
gcdHelper a b =
    if b == 0 then
        a

    else
        gcdHelper b (modBy b a)

To remove all the common factors from both the numerator and denominator I divide both by their greatest common divisor. gcd implements the Euclidean algorithm, which is an efficient method for computing the greatest common divisor (GCD) of two integers.

gcdHelper is implemented using tail-recursion so that it can be optimized into a loop.

I tested that new met the requirements using elm repl. The unit tests came later.

Convenient Constructors

Having to deal with Maybe everytime you need a rational number can become tedious. The zero and fromInt constructors make it quite easy to create the rational numbers for zero and the other integers.

zero

zero : Rational
zero =
    Rational 0 1

fromInt

fromInt : Rational
fromInt n =
    Rational n 1