Search code examples
f#icomparable

F# Compare function associated to arrays


This post is related to a previous one.

I believe that F# stores, somewhere, a method allowing to compare arrays of the same type, provided that the arrays' elements are comparable. The reason why I believe so : the Set type requires its elements to be comparable, and Set accepts arrays of comparable types (it even orders their elements when instantiated, allowing us to guess the comparison function it uses for arrays). A few examples :

let s1 = [| [| 10; 20 |]; [| 10; 19 |]; [| 10; 19; 100 |]; [| 10; 20; -100 |] |] |> Set.ofArray;;

returns

val s1 : Set<int []> =  set [[|10; 19|]; [|10; 20|]; [|10; 19; 100|]; [|10; 20; -100|]]

similarly,

let s2 = [| [| ("b", 1); ("a", 2) |]; [| ("z", 1) |]; [| ("a", 0); ("a", 0); ("a", 0) |]; [| ("b", 1); ("a", 3) |] |] |> Set.ofArray;;

returns

val s2 : Set<(string * int) []> = set [[|("z", 1)|]; [|("b", 1); ("a", 2)|]; [|("b", 1); ("a", 3)|]; [|("a", 0); ("a", 0); ("a", 0)|]]

(the comparison seems to be left to right element by element comparison if two arrays have the same length, or arr1 > arr2 if arr1.Length > arr2.Length)

Last, more convoluted, example :

type Foo = Z of int | A of int
let s3 = [| [| Z 1; A 1|]; [| A 100 |]; [| Z 1; Z 20|]; [| Z 0; Z 0; Z 0 |]; [| Z 1; Z 10|] |] |> Set.ofArray;;

returns

val s3 : Set<Foo []> = set [[|A 100|]; [|Z 1; Z 10|]; [|Z 1; Z 20|]; [|Z 1; A 1|]; [|Z 0; Z 0; Z 0|]]`

However the last example does not work, since objects aren't comparable:

let s0 = [| [| box 10; box 20 |]; [| box 10; box 19 |] |] |> Set.ofArray;;

returns

stdin(52,62): error FS0001: The type 'obj' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface`

So I hope the above shows that Set knows how to compare arrays (provided that their elements' type is comparable).

Unfortunately I am not able to access the compare method of two arrays directly :

let x1 : int[] = [| 1 |]
let x2 : int[] = [| 2 |]
let c12 = x1.CompareTo(x2);;

Gives the following error message :

 let c12 = x1.CompareTo(x2);;
 -------------^^^^^^^^^
stdin(3,14): error FS0039: The field, constructor or member 'CompareTo' is not defined.

My question : how to access the CompareTo method associated to an array object? ... If possible via a function signature arrcompare : arr1:obj -> arr2:obj -> int or arrcompare : arr1:obj -> arr2:obj -> int option (using option for when the 2 arguments are not arrays or not arrays of the same type).


Solution

  • Thanks to the answers of Asti and Kaefer, I was able to dig a little further and realized that what I needed was structural equality, which can be coded that way :

    let rec structcompare (o1 : obj) (o2:obj) : int option = 
        match (o1, o2) with
        | (:? IStructuralComparable as arr1), (:? IStructuralComparable as arr2) -> 
               if arr1.GetType() = arr2.GetType() then compare arr1 arr2 |> Some else None
        | _ -> None
    

    with the following results :

    structcompare (box [| 10; 19 |]) (box [| 10; 20 |]);;
    

    returns val it : int option = Some -1

    structcompare (box [| 10; 19; 0 |]) (box [| 10; 20 |]);;
    

    returns val it : int option = Some 1

    and

    structcompare (box [| 1 |]) (box [| "a" |]);;
    

    returns val it : int option = None