This will be a slightly longer series of posts. I thought about treating fractions as a distinct problem, and wanted to find a way to code reducing fractions in a logical and meaningful way.

The first problem was describing the type. Naturally, you can do it a as simple pair:

type Fraction = int * int let a = (-1, -2 : Fraction) // negatives?

But that design describes some issues I’m not super keen on handling, namely negative denominators. Mathematically, negative denominators aren’t all that complicated to deal with, but in my head, it seemed unnecessary to solve them, so that led to the following:

type Fraction = int * uint32 let a = (-1, 2u : Fraction) // ugh, not as clean as I'd like

Now we’ve dealt with the negative denominator problem, but that leaves us describing fractions in a sort of “planar” way. Great for talking to a professor, but I’ve always been something of a “make it clear” personality. Record types work well for this.

type Fraction = { Numerator : int; Denominator : uint32 } { Numerator = 1 ; Denominator = 2u; }

Nice and pretty. Except 0 is a valid uint32 value. Shit.

type BiggerThanZero = private BiggerThanZero of uint32 module BiggerThanZero = let create uintValue = if (uintValue > 0u ) then BiggerThanZero uintValue else failwith "No zeros" let value (BiggerThanZero u) = u type Fraction = { Numerator: int; Denominator: BiggerThanZero }

Alright. Something is breaking my “F# is less verbose” than other languages spidey-sense here, in C#.

using System; public class Fraction { public struct BiggerThanZero { public BiggerThanZero(UInt32 u) { if(u == 0u) then throw new ArgumentException("No zeros"); Value = u; } public UInt32 Value { get; } } public Fraction(int numerator, BiggerThanZero denominator) { Numerator = numerator; Denominator = denominator; } public int Numerator { get; } public BiggerThanZero Denominator { get; } }

Nope, spidey-sense was off. C# is still more code. Phew… thought I was going to have to go back to OO land. 🙂

OK… so now we have a domain object. We cannot represent an object in the domain that is “invalid” in any way, so fundamentally, our functions should be easy to reason about.

So first thing… decimals to my new “Fraction” type.

let getFraction decimalValue = let rec fract dm = let a = (d * m) let r = a % 1.0m match r with | 0.0m -> { Numerator = (int a) ; Denominator = BiggerThanZero.create (uint32 m)} | _ -> fract d (m * 10.0m) fract decimalValue 10.0m

This function goes back to our old friend, the recursive inner function. We take our input decimal value, and multiplying it by 10, and calling the result ‘a’ (shorthand for ‘amount’). Then we take ‘a’, and get the decimal part of that value by applying the modulo function to it and 1.0, and naming that value ‘r’ (shorthand for remainder). Assuming that ‘r’ is non-zero, we recurse into the loop, updating the multiplier to another factor of 10 greater than what we had before. Otherwise, we simply return a Fraction object, with the Numerator set to ‘a’, and the Denominator set to the multiple. E.G.

getFraction 0.4m;; val it : Fraction = { Numerator = 4; Denominator = BiggerThanZero 10u } getFraction 0.542m;; val it : Fraction = { Numerator = 542; Denominator = BiggerThanZero 1000u } getFraction 0.8675421m;; val it : Fraction = { Numerator = 8675421; Denominator = BiggerThanZero 10000000u }

This is the start of our Fractions work, and although it’s correct, it’s certainly got some potential error cases. Our int and uint32 bases for values could be overflowed. That’s fixed in the following.

type BiggerThanZero = private BiggerThanZero of bigint | |

module BiggerThanZero = | |

let create uintValue = | |

if (uintValue > 0I ) then BiggerThanZero uintValue | |

else failwith "No zeros" | |

let value (BiggerThanZero u) = | |

u | |

type Fraction = { Numerator: bigint; Denominator: BiggerThanZero } | |

let getFraction decimalValue = | |

let rec fract d (m : decimal) = | |

let a = (d * m) | |

let r = a % 1.0m | |

match r with | |

| 0.0m -> | |

{ Numerator = (bigint a) ; Denominator = BiggerThanZero.create (bigint m) } | |

| _ -> fract d (m * 10.0m) | |

fract decimalValue 10.0m |

Next time, we’ll deal with reducing our fractions.