The idea's from Professional F# 2.0, Chapter 17 (by Ted Neward, Aaron C. Erickson, Talbott Crowell, Richard Minerich). Because there are 4 of them I'll call them the fgang of four, or FoF.
When searching a dropdown list via a form, a web page form may return a value or nothing at all. F# handles nothing at all with None.
Suppose our database data contained:
type VacationLocation =
{ Name: string; Pop: int; Density: int; Nightlife: int }
let destinations =
[ { Name = "New York"; Pop = 9000000; Density = 27000; Nightlife = 9 }
{ Name = "Munich"; Pop = 1300000; Density = 4300; Nightlife = 7 }
{ Name = "Tokyo"; Pop = 13000000; Density = 15000; Nightlife = 3 }
{ Name = "Rome"; Pop = 2700000; Density = 5500; Nightlife = 5 } ]
Our web page filters user selections by city name, population, population density, and night life. A user will often ignore a selection. So each selection will be an option.
A first iteration of F# can handle it likewise, using the identity function id:
let getVacationPipeline nightlifeMin sizeMin densityMax searchName =
match nightlifeMin with
| Some(n) -> List.filter (fun x -> x.Nightlife >= n)
| None -> id
>> match sizeMin with
| Some(s) -> List.filter (fun x -> x.Pop / x.Density >= s)
| None -> id
>> match densityMax with
| Some(d) -> List.filter (fun x -> x.Density <= d)
| None -> id
>> match searchName with
| Some(sn) -> List.filter (fun x -> x.Name.Contains(sn))
| None -> id
This function is applied to the data like so:
let applyVacationPipeline data filterPipeline =
data
|> filterPipeline
|> List.map (fun x -> x.Name)
with filterPipeline a general function to be applied for the where clause. The last line is the select clause.
let myPipeline = getVacationPipeline (Some 5) (Some 200) (Some 8000) None applyVacationPipeline destinations myPipeline
This match ... with ... some ... None -> id is repetitive, so we abstract it out as a getFilter function. It becomes:
let getVacationPipeline2 nightlifeMin sizeMin densityMax searchName =
let getFilter filter some =
match some with
| Some(v) -> List.filter (filter v)
| None -> id
getFilter (fun nlMax x -> x.Nightlife >= nlMax) nightlifeMin
>> getFilter (fun sMax x -> x.Pop / x.Density >= sMax) sizeMin
>> getFilter (fun dMin x -> x.Density < dMin) densityMax
>> getFilter (fun sName x -> x.Name.Contains(sName)) searchName
We can invoke it like so:
let myPipeline2 = getVacationPipeline2 (Some 5) (Some 200) (Some 8000) None applyVacationPipeline destinations myPipeline2
The FoF is more detailed, beginning with an imperative version (??). I suspect every C# programmer gave up such imperative code long ago for Linq.
F# automatically handles missing data. Because we can compose our queries, each may be simply tested too.
No comments:
Post a Comment