Let’s say you have a set containing the nicknames of the guys in the band Rush, and you want to see whether a certain person is in the band. No problem:
let rush = set ["Dirk"; "Lerxst"; "Pratt"] let gotAlex = rush |> Set.contains "Lerxst" // val gotAlex : bool = true let gotAngus = rush |> Set.contains "Angus" // val gotAngus : bool = false
If, on the other hand, you have the same values in a list, things don’t go so well:
let rush = ["Dirk"; "Lerxst"; "Pratt"] let gotAlex = rush |> List.contains "Lerxst" // rush |> List.contains "Lerxst" // -------------^^^^^^^^ // // error FS0039: The value, constructor, // namespace or type 'contains' is not defined
Huh? No contains
function in the List
module? OK, what if you try it with an array?
let rush = [|"Dirk"; "Lerxst"; "Pratt"|] let gotAlex = rush |> Array.contains "Lerxst" // rush |> Array.contains "Lerxst" // --------------^^^^^^^^ // // error FS0039: The value, constructor, // namespace or type 'contains' is not defined
Still AOL! Don’t look for it in the Seq
module either; it’s not there.
Lists, Arrays, and Sequences (Oh My!)
Well, there are two ways around these limitations. First, contains
is just a special case of exists
, which is defined in all the collections modules. As F#’s modules are open, it’s easy enough to add contains
to your projects:
module List = let contains x = List.exists ((=) x) module Array = let contains x = Array.exists ((=) x) module Seq = let contains x = Seq.exists ((=) x)
And voila, all of the following now work fine:
let rushL = ["Dirk"; "Lerxst"; "Pratt"] let rushA = [|"Dirk"; "Lerxst"; "Pratt"|] let rushSeq = seq ["Dirk"; "Lerxst"; "Pratt"] let gotAlex = rushL |> List.contains "Lerxst" // val gotAlex : bool = true let gotNeil = rushA |> Array.contains "Pratt" // val gotNeil : bool = true let gotGene = rushSeq |> Seq.contains "Gene" //val gotGene : bool = false // Lists and arrays are sequences! let gotGeddy = rushL |> Seq.contains "Dirk" // val gotGeddy : bool = true let gotPeter = rushA |> Seq.contains "Peter" // val gotPeter : bool = false
The other approach you could take is to use LINQ. Opening the System.Linq
namespace grants you access to all of the Enumerable
extension methods. That means that all of the following work:
open System.Linq let rushL = ["Dirk"; "Lerxst"; "Pratt"] let rushA = [|"Dirk"; "Lerxst"; "Pratt"|] let rushSet = set ["Dirk"; "Lerxst"; "Pratt"] let rushSeq = seq ["Dirk"; "Lerxst"; "Pratt"] let gotAlex = rushL.Contains "Lerxst" // val gotAlex : bool = true let gotNeil = rushA.Contains "Pratt" // val gotNeil : bool = true let gotGeddy = rushSet.Contains "Dirk" // val gotGeddy : bool = true let gotGene = rushSeq.Contains "Gene" //val gotGene : bool = false
If you prefer your F# to be a little more idiomatic, you can define new module functions that delegate to LINQ rather than using exists
:
open System.Linq module List = let contains x (l: 'T list) = l.Contains x module Array = let contains x (a : 'T array) = a.Contains x module Seq = let contains x (s : 'T seq) = s.Contains x
Strings
Similarly, the String
module does not contain a contains
function either. You can take the same tack as you did with the other collections and define contains
in terms of exists
:
module String = let contains x = String.exists ((=) x) let gotX = "Lerxst" |> String.contains 'x' // val gotX : bool = true let gotA = "Lerxst" |> Seq.contains 'A' // val gotA : bool = false
Or you likewise have the option of using LINQ:
open System.Linq module String // String.Contains can take a char or a string, // so you have to specify which let contains (c : char) (s : string) = s.Contains c let gotX = "Lerxst" |> String.contains 'x' // val gotX : bool = true let gotA = "Lerxst" |> Seq.contains 'A' // val gotA : bool = false
Of course, because String.Contains
is overloaded to take either a string
or a char
, you have a little more flexibility if you call it as a method on the string
rather than an F# function:
"Pratt".Contains("tt") // val it : bool = true "Pratt".Contains('p') // val it : bool = false
Maps
Finally, the Map
module does have a containsKey
function …
let rushM = ["Dirk", "bass"; "Lerxst", "guitar"; "Pratt", "drums"] |> Map.ofList let gotGed = rushM |> Map.containsKey "Dirk" // val gotGed : bool = true
… but no containsValue
or contains
functions:
rushM |> Map.contains "Dirk" // rushM |> Map.contains "Dirk" // -------------^^^^^^^^ // // stdin(5,14): error FS0039: The value, constructor, // namespace or type 'contains' is not defined rushM |> Map.contains ("Dirk", "bass") // rushM |> Map.contains ("Dirk", "bass") // -------------^^^^^^^^ // // stdin(7,14): error FS0039: The value, constructor, // namespace or type 'contains' is not defined rushM |> Map.containsValue "bass" // rushM |> Map.containsValue "bass" // -------------^^^^^^^^^^^^^ // // stdin(9,14): error FS0039: The value, constructor, // namespace or type 'containsValue' is not defined
As with the other collections, we can define contains
and containsValue
in terms of exists
:
module Map = let contains (k,v) = Map.exists (fun x y -> (x,y) = (k,v)) let containsValue v = Map.exists (fun _ x -> x = v) let gotDirkOnBass = rushM |> Map.contains ("Dirk", "bass") // val gotDirkOnBass : bool = true let gotBassPlayer = rushM |> Map.containsValue "bass" // val gotBassPlayer : bool = true
You could alternatively define contains
in terms of LINQ, but it is more verbose and provides no advantage over Map.exists
. Because neither LINQ nor the IDictionary
interface provide a ContainsValue
method, defining Map.containsValue
is certainly more verbose and a little more involved than simply using Map.exists
.
NOTE: As of this writing, F# 3.1 is the latest version. According to UserVoice, F# 4.0 adds the missing contains
function to the List
, Array
, and Seq
modules, along with a few other useful functions.