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.