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