mixing functional programming approaches in an object oriented language

Post on 26-Aug-2014

1.177 Views

Category:

Technology

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

My slides from Biz Tech Delhi

TRANSCRIPT

Applying functional programming approaches in object oriented languages

Mark Needham

© ThoughtWorks 2010

C# 1.0

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 == 0){

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 == 0){

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 GreaterThan3Predicate : 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[] greaterThan3 = Filter(ints, new GreaterThan3Predicate());

interface IIntegerPredicate{

bool Matches(int value);}

bool delegate IntegerPredicate(int value);

bool Even(int value){

return value % 2 == 0;}

bool GreaterThan3(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[] greaterThan3 = Filter(ints,  new

IntegerPredicate(GreaterThan3));

C# 2.0

 

Inference

int[] ints = new int[] {1, 2, 3, 4, 5 };

int[] even = Filter(ints, new IntegerPredicate(Even));

int[] greaterThan3 = Filter(ints, new IntegerPredicate(GreaterThan3));

Inference

int[] ints = new int[] {1, 2, 3, 4, 5 };

int[] even = Filter(ints, Even);

int[] greaterThan3 = Filter(ints, GreaterThan3);

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();}

Iterators

IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> p)

{List<T> results = new List<T>();

foreach (T i in value){

if (p(i)){

results.Add(i);}

}

return results;}

Iterators

IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> p)

{foreach (T i in value){

if (p(i)){

yield return i;}

}}

Anonymous Methods

IEnumerable<int> greaterThan3 = Filter(ints, GreaterThan3);

Anonymous Methods

IEnumerable<int> greaterThan3 = Filter(ints, delegate(int value) { return value > 3; });

Anonymous Methods

int minimumValue = 3;

IEnumerable<int> greaterThan3 = Filter(ints, delegate(int value) { return value > minimumValue; });

C# 3.0

 

Lambdas

int minimumValue = 3;

IEnumerable<int> greaterThan3 = Filter(ints, delegate(int value) { return value > minimumValue; });

Lambdas

int minimumValue = 3;

IEnumerable<int> greaterThan3 = Filter(ints, value => value > minimumValue);

More Type Inference

int minimumValue = 3;

IEnumerable<int> greaterThan3 = Filter(ints, value => value > minimumValue);

More Type Inference

int minimumValue = 3;

var greaterThan3 = Filter(ints, value => value > minimumValue);

Extension Methods

int minimumValue = 3;

var greaterThan3 = Filter(ints, value => value > minimumValue);

Extension Methods

int minimumValue = 3;

var greaterThan3 = ints.Filter(value => value > minimumValue);

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 greaterThan3 = ints.Where(value => value > 3) or

var even = from value in ints                  where value % 2 == 0                  select value  var greaterThan3 = from value in ints                                       where value > 3                                                   select value                                      

Interfaces

Delegates

Anonymous methods

Lambdas

So this functional programming thing…

http://www.flickr.com/photos/stuartpilbrow/2938100285/sizes/l/

Higher order functions

var ints = new int[] {1, 2, 3, 4, 5 };

var greaterThan3 = ints.Where(value => value > 3)

var even = ints.Where(value => value % 2 == 0)

Pure functions

Immutability

Lazy evaluation

Iterators

IEnumerable<T> Filter<T>(IEnumerable<T> values, Predicate<T> p)

{foreach (T i in value){

if (p(i)){

yield return i;}

}}

Recursion & Pattern Matching

Transformational Mindset

We 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

 

Programming in the…

LargeMedium

Small

Programming in the …

LargeMedium

Small

“a high level that affects as well as crosscuts multiple classes and functions”

Programming in the …

LargeMedium

Small

“a single API or group of related APIs in such things as classes, interfaces, modules”

Programming in the …

LargeMedium

Small

“individual function/method bodies”

LargeMedium

Small

LargeMedium

Small

Abstractions over common operations means less code and less chances to make mistakes

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()

 

  

       people.Select(person => person.Age)            .Aggregate(0, (totalAge, nextAge) =>                     nextAge % 2 == 0                         ? nextAge + totalAge                         : totalAge)

Joining

 

 

          people.Join(addresses,                           person => person.PersonId,                  address => address.PersonId,                     (person, address) => new {                                         person, address})

Embrace the collection

 

 var first = “Mark”;var middle = “Harold”;var surname = “Needham”;

var fullname = first + “ “ + middle + “ “ + surname;

or

var names = new[] {first, middle, surname};var fullname = String.Join(“ “, names);

 public class SomeObject{ public SomeObject(string p1, string p2, string p3) { if(p1 == null) throw new Exception(…); if(p2 == null) throw new Exception(…); if(p3 == null) throw new Exception(…);

// rest of constructor logic }}

 public class SomeObject{ public SomeObject(string p1, string p2, string p3) { var parameters = new[] {p1, p2, p3}; if(parameters.Any(p => p = null) throw new Exception(…).

// rest of constructor logic }}

LargeMedium

Small

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);

}}

 

Maybe?

 

public interface Maybe<T>{

bool HasValue();T Value();

}

 

public class Some<T> : Maybe<T>{

private readonly T theThing;

public Some(T theThing){

this.theThing = theThing;}

public bool HasValue (){

return true;}

public T Value(){

return theThing;}

}

 public class None<T> : Maybe<T>{

public bool HasValue (){

return false;}

public T Value(){

throw new NotImplementedException();}

}

  public class Some{

public static Some<T> Thing<T>(T thing) : where T : class

{return new Some<T>(thing);

}}

 

public class No{

public static None<T> Thing<T>()

{return new None<T>();

}}

 

public static class MaybeExtensions{

public static Maybe<T> Maybify<T>(this T source)where T : class

{if(source == null)

return No.Thing<T>();

return Some.Thing(source);}

}

recordFromDatabase.Maybify():

 

public class FooService{

public Foo FindOrCreate(int fooId){

var foo = fooRepository.Find(fooId);

if(foo.HasValue()){

return foo.Value();}

return fooRepository.Create(fooId);}

}

 

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);

}

 

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 => (v, f, e) => m.AddErrorIfNotEqualTo(v,true, f, e));

AddErrorIf(() => person.HasChildren, modelState, m => (v, f, e) => m.AddErrorIfNull(v, f, e));

 

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); 

if we have side effects  then favour foreach construct

foreach(var item in items){        itemRepository.Save(item);}

rather than:

items.ToList().ForEach(item => itemRepository.Save(item));

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

 

public Money CalculateSomething(Func<Customer, DateTime, Money> calculation)

{// do some calculation

}

public delegate Money PremiumCalculation(Customer customer, DateTime renewalDate);

public Money CalculateSomething(PremiumCalculation calculation)

{// do some calculation

}

 

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…

 

Mark Needhammneedham@thoughtworks.com

© ThoughtWorks 2010

top related