Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine if any kind of list, sequence, array, or IEnumerable is empty

I am writing a Xamarin.Forms app using XAML for my views, and I am trying to write an IValueConverter whose job should be returning false if the input is "empty" for types where those semantics make sense (strings/lists/sequences/arrays/IEnumerables). I have started with the following, which returns false for empty strings, but I can't figure out how to extend this to lists, sequences, arrays, and IEnumerables:

type FalseIfEmptyConverter() =
  interface IValueConverter with 
    member __.Convert(value:obj, _, _, _) = 
      match value with
      | :? string as s -> (s <> "" && not (isNull s)) |> box
      // TODO: extend to enumerables
      | x -> invalidOp <| "unsupported type " + x.GetType().FullName

    member __.ConvertBack(_, _, _, _) =
      raise <| System.NotImplementedException()

Things I've tried that don't work:

  • :? list<_> does not match a (boxed) list (at least not of ints) and produces a warning This construct causes code to be less generic than indicated by its type annotations. The type variable implied by the use of a '#', '_' or other type annotation at or near [...] has been constrained to be type 'obj'
  • :? list<obj> does not produce the warning, but also doesn't match a boxed list of ints
  • It's the same with :? seq<_> and :? seq<obj>
  • It's the same with :? System.Collections.Generic.IEnumerable<obj> and IEnumerable<_> (and if I place it below a similar seq match as given above, it warns that the rule will never be matched, which makes sense since AFAIK seq corresponds to IEnumerable)
like image 201
cmeeren Avatar asked Nov 14 '17 08:11

cmeeren


1 Answers

Using Foggy Finder's idea to use the non-generic IEnumerable:

let isEmpty (x:obj) =
    match x with
    | null -> true
    | :? System.Collections.IEnumerable as xs -> xs |> Seq.cast |> Seq.isEmpty
    | _ -> invalidOp <| "unsupported type " + x.GetType().FullName

isEmpty "" // true
isEmpty [] // true
isEmpty (set []) // true
isEmpty [||] // true
isEmpty null // true

isEmpty "a" // false
isEmpty [|1|] // false

isEmpty 1 // exception

All of the types that you want to test are sub-types of Seq<'a>, which is exactly the same as IEnumerable<'a> (including string, which is a seq<char>). But this is also sub-type of a non-generic type called IEnumerable (note the lack of type parameter). This is similar to an IEnumerable<obj>, where every item has been boxed. This is why we can cast all of these to IEnumerable, then use Seq.cast to convert that into IEnumerable<obj> so that we can use Seq.empty, which only works on the generic type.

like image 142
TheQuickBrownFox Avatar answered Oct 25 '22 03:10

TheQuickBrownFox