Mixing Functional and Object Oriented approaches to programming in C#
Mike Wagg & Mark Needham
C# 1.0
http://www.impawards.com/2003/posters/back_in_the_day.jpg
int[] ints = new int[] {1, 2, 3, 4, 5}
int[] Filter(int[] ints){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (i % 2 == 0)
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
int[] ints = new int[] {1, 2, 3, 4, 5}
int[] Filter(int[] ints){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (i >3)
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
int[] Filter(int[] ints){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (i % 2 == 0)
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
int[] Filter(int[] ints){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (i > 3)
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
interface IIntegerPredicate{
bool Matches(int value);}
class EvenPredicate : IIntegerPredicate{
bool Matches(int value)
{
return value % 2 == 0;
}}
class GreaterThanThreePredicate : IIntegerPredicate{
bool Matches(int value)
{
return value > 3;
}}
int[] Filter(int[] ints, IIntegerPredicate predicate){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (predicate.Matches(i))
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
int[] ints = new int[] {1, 2, 3, 4, 5 };
int[] even = Filter(ints, new EvenPredicate());
int[] greaterThanThree = Filter(ints, new GreaterThanThreePredicate());
interface IIntegerPredicate{
bool Matches(int value);}
bool delegate IntegerPredicate(int value);
bool Even(int value){
return value % 2 == 0;}
bool GreaterThanThree(int value){
return value > 3;}
int[] Filter(int[] ints, IntegerPredicate predicate){ ArrayList results = new ArrayList();
foreach (int i in ints) {
if (predicate(i))
{
results.Add(i);
} }
return results.ToArray(typeof(int));}
int[] ints = new int[] {1, 2, 3, 4, 5 };
int[] even = Filter(ints,
new IntegerPredicate(Even));
Int[] greaterThanThree = Filter(ints,
new IntegerPredicate(GreaterThanThree));
C# 2.0
Inference
int[] ints = new int[] {1, 2, 3, 4, 5 };
int[] even = Filter(ints, new IntegerPredicate(Even));
Int[] greaterThanThree = Filter(ints, new IntegerPredicate(GreaterThanThree));
Inference
int[] ints = new int[] {1, 2, 3, 4, 5 };
int[] even = Filter(ints, Even);
Int[] greaterThanThree = Filter(ints, GreaterThanThree);
The compiler can infer what the type of the delegate is so we don’thave to write it.
Generics
delegate bool IntegerPredicate(int value);
Generics
delegate bool Predicate<T> (T value);
Generics
int[] Filter(int[] ints, IntegerPredicate predicate){
ArrayList results = new ArrayList();
foreach (int i in ints){
if (predicate(i)){
results.Add(i);}
}
return results.ToArray(typeof(int));}
Generics
T[] Filter<T>(T[] values, Predicate<T> predicate){
List<T> results = new List<T>();
foreach (T i in value){
if (predicate(i)){
results.Add(i);}
}
return results.ToArray();}
Generics
IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> predicate)
{List<T> results = new List<T>();
foreach (T i in value){
if (predicate(i)){
results.Add(i);}
}
return results;}
Iterators
IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> predicate)
{List<T> results = new List<T>();
foreach (T i in value){
if (predicate(i)){
results.Add(i);}
}
return results;}
Iterators
IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> predicate)
{foreach (T i in value){
if (predicate(i)){
yield return i;}
}}
Anonymous Methods
IEnumerable<int> greaterThanThree = Filter(ints, GreaterThanThree);
Anonymous Methods
IEnumerable<int> greaterThanThree = Filter(ints, delegate(int value) { return value > 3; });
Anonymous Methods
int minimumValue = 3;
IEnumerable<int> greaterThanThree = Filter(ints, delegate(int value) { return value > minimumValue; });
Anonymous methods add support for closures. The delegate captures the scope it was defined in.
C# 3.0
Lambdas
int minimumValue = 3;
IEnumerable<int> greaterThanThree = Filter(ints, delegate(int value) { return value > minimumValue; });
Lambdas
int minimumValue = 3;
IEnumerable<int> greaterThanThree = Filter(ints, value => value > minimumValue);
More Type Inference
int minimumValue = 3;
IEnumerable<int> greaterThanThree = Filter(ints, value => value > minimumValue);
More Type Inference
int minimumValue = 3;
var greaterThanThree = Filter(ints, value => value > minimumValue);
Extension Methods
int minimumValue = 3;
var greaterThanThree = Filter(ints, value => value > minimumValue);
Extension Methods
int minimumValue = 3;
var greaterThanThree = ints.Filter(value => value > minimumValue);
Anonymous Types
var anonymous = new{
Foo = 1,Bar = “Bar”
}
LINQ
LINQ
New delegates in System namespaceAction<T>, Action<T1, T2>, Func<TResult>, Func<T1, TResult> etc.
LINQ
New delegates in System namespaceAction<T>, Action<T1, T2>, Func<TResult>, Func<T1, TResult> etc.
System.LinqExtension methods Where, Select, OrderBy etc.
LINQ
New delegates in System namespaceAction<T>, Action<T1, T2>, Func<TResult>, Func<T1, TResult> etc.
System.LinqExtension methods Where, Select, OrderBy etc. Some compiler magic to translate sql style code to method calls
LINQ
var even = ints.Where(value => value % 2 == 0) var greaterThanThree = ints.Where(value => value > minimumValue) or
var even = from value in ints where value % 2 == 0 select value var greaterThanThree = from value in ints where value > minimumValue select value
A (little) bit of theory
http://www.flickr.com/photos/stuartpilbrow/2938100285/sizes/l/
Higher order functions
Immutability
Lazy evaluation
Recursion & Pattern Matching
Transformational MindsetWe can just pass functions around instead in most cases- find an example where it still makes sense to use the GOF approach though.
Input -> ??? -> ??? -> ??? -> Output
http://www.emt-india.net/process/petrochemical/img/pp4.jpg
So why should you care?
Functional can fill in the gaps in OO code
Abstractions over common operations means less code and less chances to make
mistakes
So what do we get out of the box?
Projection
people.Select(person => person.Name)
people.SelectMany(person => person.Pets)
Restriction
people.Where(person => person.HasPets)
Partitioning
people.Take(5)
people.Skip(5)
people.TakeWhile(person => person.Name != "David")
people.SkipWhile(person => person.Name != "David")
Set
people.Select(person => person.Name).Distinct()
people.Union(someOtherPeople)
people.Intersect(someOtherPeople)
people.Except(someOtherPeople)
Ordering and Grouping
people.OrderBy(person => person.Name)
people.GroupBy(person => person.Name)
Aggregation
people.Count()
people.Select(person => person.Age).Sum()
people.Select(person => person.Age).Min()
people.Select(person => person.Age).Max()
people.Select(person => person.Age).Average()
Things can get more complex
people.Select(person => person.Age) .Aggregate(0, (totalAge, nextAge) => nextAge % 2 == 0 ? nextAge + totalAge : totalAge)
people.Join(addresses, person => person.PersonId, address => address.PersonId, (person, address) => new { person, address})
We can just pass functions around instead in most cases- find an example where it still makes sense to use the GOF approach though.
public class SomeObject{
private readonly IStrategy strategy;
public SomeObject(IStrategy strategy){
this.strategy = strategy;}
public void DoSomething(string value){
strategy.DoSomething(value);}
}
public class Strategy : IStrategy{
public void DoSomething(string value){
// do something with string}
}
public class SomeObject{
private readonly Action<string> strategy;
public SomeObject(Action<string> strategy){
this.strategy = strategy;}
public void DoSomething(string value){
strategy(value);}
}
Hole in the middle pattern
public class ServiceCache<Service>{
protected Res FromCacheOrService <Req, Res>(Func<Res> serviceCall, Req request)
{ var cachedRes = cache.RetrieveIfExists(
typeof(Service), typeof(Res), request);
if(cachedRes == null) {
cachedRes = serviceCall();cache.Add(typeof(Service), request,
cachedRes);}
return (Res) cachedRes;}
}
public class CachedService : ServiceCache<IService>{
public MyResult GetMyResult(MyRequest request){
return FromCacheOrService(() => service.GetMyResult(request), request);
}}
Passing functions around
private void AddErrorIf<T>(Expression<Func<T>> fn, ModelStateDictionary modelState,
Func<ModelStateDictionary, Func<T,string, string, bool>> checkFn)
{var fieldName = ((MemberExpression)fn.Body).Member.Name;var value = fn.Compile().Invoke();var validationMessage = validationMessages[fieldName]);
checkFn.Invoke(modelState)(value, fieldName, validationMessage);}
AddErrorIf(() => person.HasPets, modelState, m => (value, field, error) => m.AddErrorIfNotEqualTo(value,true, field,
error));
AddErrorIf(() => person.HasChildren, modelState, m => (value, field, error) => m.AddErrorIfNull(value, field, error));
Continuation Passing Style
static void Identity<T>(T value, Action<T> k) {
k(value); }
Identity("foo", s => Console.WriteLine(s));
Identity("foo", s => Console.WriteLine(s));
as compared to
var foo = Identity(“foo”);Console.WriteLine(foo);
public ActionResult Submit(string id, FormCollection form) { var shoppingBasket = CreateShoppingBasketFrom(id, form); return IsValid(shoppingBasket, ModelState, () => RedirectToAction("index", "ShoppingBasket", new { shoppingBasket.Id} ), () => LoginUser(shoppingBasket, () => { ModelState.AddModelError("Password", "User name/email address was incorrect - please re-enter"); return RedirectToAction("index", ""ShoppingBasket",
new { Id = new Guid(id) }); }, user => { shoppingBasket.User = user; UpdateShoppingBasket(shoppingBasket); return RedirectToAction("index", "Purchase",
new { Id = shoppingBasket.Id }); } )); }
private RedirectToRouteResult IsValid(ShoppingBasket shoppingBasket, ModelStateDictionary modelState, Func<RedirectToRouteResult> failureFn,
Func<RedirectToRouteResult> successFn) { return validator.IsValid(shoppingBasket, modelState) ? successFn() : failureFn(); }
private RedirectToRouteResult LoginUser(ShoppingBasket shoppingBasket, Func<RedirectToRouteResult> failureFn, Func<User,RedirectToRouteResult> successFn) {
User user = null; try {
user = userService.CreateAccountOrLogIn(shoppingBasket); } catch (NoAccountException) {
return failureFn(); } return successFn(user);
}
http://www.thegeekshowpodcast.com/home/mastashake/thegeekshowpodcast.com/wp-content/uploads/2009/07/wtf-cat.jpg
So what could possibly go wrong?
http://icanhascheezburger.files.wordpress.com/2009/06/funny-pictures-cat-does-not-think-plan-will-fail.jpg
Hard to diagnose errors
var people = new [] {
new Person { Id=1, Address = new Address { Road = "Ewloe Road" }}, new Person { Id=2},
new Person { Id=3, Address = new Address { Road = "London Road"}} };
people.Select(p => p.Address.Road);
Null Reference Exception on line 23
http://www.flickr.com/photos/29599641@N04/3147972713/
public T Tap(T t, Action action) { action(); return t;}
people .Select(p => Tap(p, logger.debug(p.Id)) .Select(p => p.Address.Road);
Readability
Lazy evaluation can have unexpected consequences
IEnumerable<string> ReadNamesFromFile() { using(var fileStream = new FileStream("names.txt", FileMode.Open)) using(var reader = new StreamReader(fileStream)) { var nextLine = reader.ReadLine(); while(nextLine != null) { yield return nextLine; nextLine = reader.ReadLine(); } } }
IEnumerable<Person> GetPeople() { return ReadNamesFromFile() .Select(name => new Person(name)); }
IEnumerable<Person> people = GetPeople(); foreach (var person in people) { Console.WriteLine(person.Name); } Console.WriteLine("Total number of people: " + people.Count());
Encapsulation is still important
Total salary for a company company.Employees.Select(employee => employee.Salary) .Sum()
This could lead to duplication
What if we add rules to the calculation?
Who should really have this responsibility?.Sum()
Linq isn't the problem here, it's where we have put it
Company naturally has the responsibility so encapsulate
the logic here
class Company{ public int TotalSalary { get { return employees.Select(e => e.Salary).Sum(); } } }
Sometimes we need to go further
If both Company and Division have employees do we
duplicate the logic for total salary?
IEnumerable<T> and List<T> make collections easy but
sometimes it is still better to create a class to represent a
collection
class EmployeeCollection{ private List<Employee> employees; public int TotalSalary { get { return employees.Select(e => e.Salary).Sum(); } }}
In conclusion…
Mike [email protected]
Mark [email protected]
Top Related