tags:

views:

118

answers:

3

Hello,

I am trying to test equality of two elements. Why do I get the error: "does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion", in the first pattern match:

    let eq a b = 
        match (a,b) with 
        | :? (seq<_>*seq<_>) -> Seq.map2( fun xA xB -> xA=xB ) a b 
                                |> Seq.fold( fun res elem -> res && elem ) true
        | :? _ -> a=b

Thanks !

A: 

Warning: this is rather ugly. I'm very curious about more elegant approaches.

There is no (seq*seq) type. You'll have to test each parameter individually. Something like

match (a,b) with
| (:? seq<_> as seqa), (:? seq<_> as seqb) -> ...

But this gives a compiler error, because the types of a and b need more type annotation. But this would constrain the parameters in such a way that you cant do something like eq 2 2.

The snippet below would fix this:

let eq a b = 
    match (box a, box b) with 
    | (:? seq<_> as seqa), (:? seq<_> as seqb) -> 
        printfn "comparing sequences..."
        Seq.map2 (fun xA xB -> xA = xB) seqa seqb |> Seq.forall id
    | _ -> printfn "comparing default..."
           a=b 

But the result of comparing two sequences is not what's expected:

> eq {1..10} {1..10};;
comparing default...
val it : bool = false

It jumps to the second match clause. This is because the compiler restricted the types seq<_> to seq and it's fed two seq.

In order to compare two sequences, they need to be converted to Seq's like this:

> eq1 (Seq.map box {1..10}) (Seq.map box {1..10});;
comparing sequences...
val it : bool = true
> eq1 (Seq.map box {1..10}) (Seq.map box {1L..10L});;
comparing sequences...
val it : bool = false

This said, I think it's a rather ugly hack. I'd suggest writing a sequence comparison function that only tests sequences.

cfern
A: 

I'm not sure why do you need this - it may be easier to store the two sequences in a collection type that automatically implements structural comparsion (element-wise compare) such as ordinary F# list.

However, you could write this using untyped IEnumerable this gives you a way to avoid the need for specifying generic type parameter for seq<_> in the pattern matching (which is not easily possible). You need a simple helper to convert non-generic IEnumerable to seq<obj>:

open System.Collections

let ofUntyped (a:IEnumerable) =
  seq { let en = a.GetEnumerator() 
        while en.MoveNext() do
          yield en.Current }

[EDIT: You can use Seq.cast<obj> instead of the helper I wrote]

The rest of the code is almost the same as your original version. You just need to look for IEnumerable in the pattern matching and then convert the sequence using ofUntyped:

let eq (a:obj) (b:obj) =  
  match a, b with  
  | (:? IEnumerable as ae), (:? IEnumerable as be) -> 
    Seq.map2( fun xA xB -> xA = xB) (ofUntyped ae) (ofUntyped be)
      |> Seq.fold( fun res elem -> res && elem ) true 
  | _ -> a = b 
Tomas Petricek
I believe 'Seq.cast<obj>' is identical to your 'ofUntyped'. Also LINQ's 'Cast' and 'OfType'.
YotaXP
A: 

Why do I get the error: "does not have any proper subtypes and cannot be used as the source of a type test or runtime coercion", in the first pattern match

Because the type System.Tuple<System.Collections.Generic.IEnumerable<System.Object>,System.Collections.Generic.IEnumerable<System.Object>>, a.k.a. (seq<obj>*seq<obj>) does not have any subtypes (the Tuple class is sealed).

Brian
Actually, to be pedantic I think it's because `'a * 'b` doesn't have any subtypes. That is, `match obj() with | :? (seq<_>*seq<_>) -> 0` compiles, but `match (1,'c') with | :? obj -> 0` doesn't.
kvb
Okay but but what about casting a KeyPairValue, for example the below returns false: let d = new Dictionary<int,int list>(); d.Add(2,[3;4;5;6]) d.Add(0,[3;5;2]) let f = box(Seq.head d) f :? KeyValuePair<_,_>Thanks
jlezard
You said 'box', so you're trying to match against `obj`, which has plenty of subtypes.
Brian