cmeeren
cmeeren

Reputation: 4210

Can any .NET type/interface be pattern matched as a list?

I tried pattern matching .NET list types (e.g. IList<> and List<>):

match myIList with
| [] -> ... 

I got an error saying The expression was expected to have type IList<...> but here has type 'a list. I get why it doesn't work - evidently, the types aren't compatible (I would guess that the F# list type does not implement IList<>).

What I'm wondering is, is there any BCL list types that can be pattern matched against without having to throw in a Seq.toList in the match expression?

Upvotes: 1

Views: 226

Answers (3)

scrwtp
scrwtp

Reputation: 13577

No, the syntax used for constructing and deconstructing an F# list is defined specifically for that type, and that type only (those operators are actually used as case labels for list that get special treatment from the compiler). This is how the list type is defined in the core library:

type List<'T> = 
   | ([])  :                  'T list
   | (::)  : Head: 'T * Tail: 'T list -> 'T list

F# list is fundamentally different from System.Collections.Generic.List - it's a cons list, and this nature is reflected in the syntax for construction/deconstruction.

System.Collections.Generic.List on the other hand is a collection that can grow in size, but is backed by an array and has the performance characteristics of an array - in fact its F# name (ResizeArray) is a far more fitting one.

IList is an interface for a mutable list, whereas F# list is immutable - it doesn't make sense for it to implement IList.

For both of those types - deconstructing them into head and tail in the same way it's done for F# lists simply doesn't reflect the reality of what they are, and would be an unnatural operation (much in the same fashion that indexing into an F# list is an unnatural operation).

Upvotes: 3

Kenneth Ito
Kenneth Ito

Reputation: 5261

From the bcl, I believe arrays are the only collection type that can be matched on without requiring Seq.toList

See https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching#array-pattern

let vectorLength vec =
    match vec with
    | [| var1 |] -> var1
    | [| var1; var2 |] -> sqrt (var1*var1 + var2*var2)
    | [| var1; var2; var3 |] -> sqrt (var1*var1 + var2*var2 + var3*var3)
    | _ -> failwith "vectorLength called with an unsupported array size of %d." (vec.Length)

printfn "%f" (vectorLength [| 1. |])
printfn "%f" (vectorLength [| 1.; 1. |])
printfn "%f" (vectorLength [| 1.; 1.; 1.; |])
printfn "%f" (vectorLength [| |] )

Upvotes: 1

Fyodor Soikin
Fyodor Soikin

Reputation: 80744

F# doesn't do automatic type coercion, like C# does. This means that you can't, for example, compare two values of different types, even if one of them is a subtype of the other.

let x : string = "abc"
let y : obj = box "abc"
let eq = x = y  // Type mismatch error here

Applying this to your specific case, you can't match a non-list value on a list, even if that value was somehow a subtype of list.

However, if you're really interested in this kind of matching, you could write yourself a custom matcher (also known as "active pattern"), which would take seq<'t> and match it with regular F# lists:

let (|IsList|) l = Seq.toList l

Such matcher is usable with anything that can be a seq - even, for example, a string:

match "abcd" with
| IsList [] -> "Empty string"
| IsList ('a'::_) -> "Starts with an a"
| IsList _ -> "Something else"

Works for normal lists, too:

match [1,2,3] with
| IsList [1,2,3] -> "One, two, three"
| IsList [] -> "Empty list"
| IsList _ -> "Huh?"

Upvotes: 3

Related Questions