Type test in F# on sequences, general equality test
I am trying to test equality of two elements. Why do I get the error:开发者_如何学Go "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 !
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).
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.
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
精彩评论