I thought hard about an appropriate introduction post into the world of F#; and indeed many ideas came to mind. I could blog about currying, function composition, lazy evaluation, and all the other (for many a c# programmer) mystical and ambiguous concepts of functional programming.
Instead, I opted to introduce F# using the decorator pattern example from my previous blog. I thought it would be a nice ice breaker for the uninitiated in functional languages and for those of us, mostly OO practitioners and programmers, who may actually appreciate F#’s ability to be very terse and expressive when it comes to writing object oriented code.
Before presenting the F# version of the query wrappers, I will very briefly cover some key syntactical concepts to help you read the code in this post.
Code Formatting and Indentation:
Something to note about F#, is that it is sensitive to code indentation and line breaks, so indenting the code is not an aesthetic imperative for code readability, it actually is required to compile the code correctly. For a list of F# coding standard please refer to this msdn article“let” Bindings
The let keyword is used to associate an identifier with a value or a function. Very simple, but powerful. Here are a few examples:let simpleFloatValue = 1.0f let squaredFunction x = x * x //let is also used inside type constructors type Someclass(name:string) = let _name = name member this.GetName = name.ToUpper()
Type Annotation:
In F#, variables’ types are implicitly inferred at compile time. Optionally, we can provide explicit parameter type. So the following two declarations are identical:let Adder x y = x + y let Adder (x:int) (y:int) : int = x + y
Generics:
Here are few examples of how F# supports generics. The first two lists are of type int and string respectively. The third example, is a generic function of type T.let myIntList : int list = [1; 2; 3]; let myStringList : List<string> = ["1"; "2"; "3"]; let myFunction<'T> (t: 'T) : unit = printf " value of generic parameter is: %A" t;
Unit Type:
In F# the unit type represents the absence of a specific value. So for expressions that do not evaluate to a value, the return type is said to be of type unit. The equivalent concept in C# or C++ is the void type.Pipeline Operator “|>”:
Pipelining enables to write a chain of function calls, such that the output of one function is the input to the following one. This successive call chain is referred to as function pipelining. So the result of the following call chain will be 101. 10 passed to the squared function (becomes 100) and then passed to the incremented function to become 101.let squared x = x * x let increment x = x + 1 let result = 10 |> squared |> increment
Query Wrappers a la F#:
With no further ado, I list here the F# replica of the C# example included in the previous post. Please note here, that I did not use LINQ query, instead I used simple lists. Fist some data setup, a dummy list of 5 employees:type Employee = { Name : string; Age : int; IsManager : bool} let emp1:Employee = {Name = "emp1"; Age = 25; IsManager = false}; let emp2:Employee = {Name = "emp2"; Age = 50; IsManager = true}; let emp3:Employee = {Name = "emp3"; Age = 42; IsManager = false}; let emp4:Employee = {Name = "emp4"; Age = 31; IsManager = false}; let emp5:Employee = {Name = "emp5"; Age = 37; IsManager = true}; let employees = [emp1;emp2;emp3;emp4;emp5];
And then the abstractions and the query wrappers.
[<AbstractClass>] type QueryComponent<'T>() = abstract member Query : unit -> 'T list [<AbstractClass>] type QueryWrapper<'T>(qc:QueryComponent<'T>) = inherit QueryComponent<'T>() let _component = qc member self.CallTrailer : 'T list = _component.Query() type EmployeeQuery() = inherit QueryComponent<Employee>() override self.Query() = employees type ManagersQuery(qc) = inherit QueryWrapper<Employee>(qc) override self.Query() = base.CallTrailer |> List.filter (fun e -> e.IsManager) type EmployeeAgeQuery(qc, age) = inherit QueryWrapper<Employee>(qc) override self.Query() = base.CallTrailer |> List.filter (fun e -> e.Age > age)
That’s basically it!! To test the code, use the F# interactive tool in visual studio. Here are a couple of examples to try:
//Get All Managers let managers = ManagersQuery(EmployeeQuery()).Query(); //Get All Employees Older Than 35 let employeesOlderThan35 = EmployeeAgeQuery(EmployeeQuery(), 35).Query()
The results of running the aforementioned two queries will return respectively, the following:
val managers : Employee list = [{Name = "emp2"; Age = 50; IsManager = true;}; {Name = "emp5"; Age = 37; IsManager = true;}] val employeesOlderThan35 : Employee list = [{Name = "emp2"; Age = 50; IsManager = true;}; {Name = "emp3"; Age = 42; IsManager = false;}; {Name = "emp5"; Age = 37; IsManager = true;}]
Recap:
Again I fully realize that such an example is probably not the best introduction into the world of functional programming but let it serve as an prelude for what is yet to come. The following Blog post will solve the same problem using purely functional approach exposing more of the interesting features of F#.Enjoy the #-ness!
No comments:
Post a Comment