6

In F# can I represent a restricted type without defining a class? Lets say I want to represent all the pairs of number where the first divides the second.

in C# I could do:

class PairDivides
{
    int a {get;private set;}
    int b {get;private set;}

    PairDivides(int first, int second)
    {
        if (a % b != 0)
        {
            throw new Exception();
        }

        this.a = first;
        this.b = second;
    }
}

Now it's impossible to create a PairDivides instance where b doesn't divide a...

Can this be done in F# using only the functional constructs (record, discriminated union, maybe active patterns, etc.)?

I want to be able to create and receive something like these pairs being certain that they are constructed correctly.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
AK_
  • 7,981
  • 7
  • 46
  • 78

1 Answers1

6

You can do this by making the type private. The only downside is that you then need to provide functions to access the data:

module Pairs =
    type PairDivides = private { a: int; b: int }

    let createPairDivides a b =
        match a % b with
        | 0 -> Some { PairDivides.a = a ; b = b }
        | _ -> None

    let print div =
        printfn "{%d %d}" div.a div.b

    let tryPrint div =
        match div with
        | Some a -> print a
        | None -> printfn "None"

let a = Pairs.createPairDivides 2 2

let b = a.Value

// This is inaccessible: b.a

Pairs.createPairDivides 2 2 |> Pairs.tryPrint
Pairs.createPairDivides 2 3 |> Pairs.tryPrint

By providing a function to create the pair, and functions to use or extract from it as necessary, you completely eliminate the ability to create invalid pairs (you'll get None back instead of a bad pair) without using exceptions.

The disadvantage is you need to provide mechanisms to extract the values from the pairs, as the type is now inaccessible when used outside of your current module.

That being said, there's nothing wrong with doing this via classes either. You can get the same level of enforcement via creating a class, if you prefer:

type PairDivides private (a,b) =
    member __.A = a
    member __.B = b

    static member Create a b =
        match a % b with
        | 0 -> Some(PairDivides(a,b))
        | _ -> None


PairDivides.Create 2 2 |> printfn "%A"
PairDivides.Create 2 3 |> printfn "%A"
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Nice answer. Worth noting though, I think, that you could actually combine the two concepts and attach a pair of properties to the record type directly. That would let you put the creation logic and other functions in a module as in your first example but still expose `A` and `B`. – TheInnerLight Jan 13 '16 at 22:52