Mixing functional and object oriented approaches to programming in C# Mark Needham
May 10, 2015
Mixing functional and object oriented approaches to
programming in C#
Mark Needham
A bit of context
ThoughtWorks delivery projects
Web applications with somewhat complicated domains
5-15 developers on a team
Projects running for 6-12 months
All this means that we want to write code which is…
Easy to understand
Easy to change
Which leads us to what this talk is all about…
Organisation of code
‘The Lush Landscape of Languages’ - The ThoughtWorks Anthology
Rebecca Parsons
How might functional programming help us with that?
First class functions
“A programming language is said to support first class functions if functions
can be created during the execution of a program, stored in data structures,
passed as arguments to other functions, and returned as the values of other
functions”
Wikipedia
Immutability
Lazy evaluation
Recursion
Pattern matching
This is all very cool but…
Object Oriented design still has its place
Encapsulation
Abstraction
So how do these two paradigms work together?
Programming in the small/medium/large
http://weblogs.asp.net/podwysocki/archive/2009/12/14/going-hybrid-implementing-a-shopping-cart-in-f.aspx
In the large…
“a high level that affects as well as crosscuts multiple
classes and functions”
In the medium…
“a single API or group of related APIs in such things as classes, interfaces, modules”
In the small…
“individual function/method bodies”
Large
Medium
Small
Large
Medium
Small
LINQ
a.k.a.Functional collection parameters
http://billsix.blogspot.com/2008/03/functional-collection-patterns-in-ruby.html
“powerful abstractions over collections”
“the use of high-order functions is extremely useful for separating the collection's concerns from the user
of the collection”
for loop becomes less useful
Don’t just use ForEach!
Code becomes more declarative
Declarative?
“a statement specifies some aspect of the desired answer with no notion of how that answer is to
be determined”
‘The Lush Landscape of Languages’ - The ThoughtWorks Anthology
Rebecca Parsons
Requires a mental shift from imperative thinking
Transformational mindset
Patrick Logan in the comments sectionhttp://www.markhneedham.com/blog/2010/01/20/functional-collectional-parameters-some-thoughts/
Current Input
???
???
???
Desired Output
Going from one collection to another
var words = new List<string> { “hello”, “world” };
var upperCaseWords = new List<string>();foreach (var word in words){
upperCaseWords.Add(word.ToUpper());}
a.k.a. map
QuickTime™ and a decompressor
are needed to see this picture.
“hello”, “world”
????
“HELLO”, “WORLD”
“hello”, “world”
Select
“HELLO”, “WORLD”
var words = new List<string> { “hello”, “world” };
var upperCaseWords = words.Select(w => w.ToUpper());
Remove values we don’t want
var words = new List<string> {“hello”, “world”};
var wordsWithH = new List<string>();foreach (var word in words){
if(word.Contains(“h”)wordsWithH.Add(word);
}
a.k.a. filter
QuickTime™ and a decompressor
are needed to see this picture.
“hello”, “world”
????
“hello”
“hello”, “world”
Where
“hello”
var words = new List<string> {“hello”, “world”} ;
var wordsWithH = words.Where(w => w.Contains(“h”));
Summing some values
var values = new List<int> { 1,2,3 };
var total = 0;foreach (var value in values){
total += value;}
a.k.a. reduce
QuickTime™ and a decompressor
are needed to see this picture.
1, 2, 3
???
6
1, 2, 3
Sum
6
var values = new List<int> { 1,2,3 };
var total = values.Sum(v => v);
Some examples from projects
Getting the first value that matches a criteria
Foo(“mark”, true), Foo(“dave”, false), Foo(“mike”, true)
????
Foo(“mark”, true)
Foo(“mark”, true), Foo(“dave”, false), Foo(“mike”, true)
Where
Foo(“mark”, true), Foo(“mike”, true)
???
Foo(“mark”, true)
Foo(“mark”, true), Foo(“dave”, false), Foo(“mike”, true)
Where
Foo(“mark”, true), Foo(“mike”, true)
First
Foo(“mark”, true)
var foos = new List<Foo> { new Foo(“mark”, true), new Foo(“dave”, false), new Foo(“mike”, true) };
var firstSpecialFoo = foos. Where(f => f.HasSpecialFlag()). First());
var foos = new List<Foo> { new Foo(“mark”, true), new Foo(“dave”, false), new Foo(“mike”, true) };
var firstSpecialFoo = foos.First(f => f.HasSpecialFlag());
Removing some columns from a dataset
a,b,c,d,e,f
????
“a,d,e,f”
a,b,c,d,e,f
Where
IEnumerable of a, d, e, f
???
“a,d,e,f”
a,b,c,d,e,f
Where
IEnumerable of a, d, e, f
???
String.Join on [a, d, e, f]
“a,d,e,f”
a,b,c,d,e,f
Where
IEnumerable of a, d, e, f
ToArray()
String.Join on [a, d, e, f]
“a,d,e,f”
var aRow = “a, b, c, d, e, f”;
var newRow = String.Join(“,”, aRow
.Split(‘,’) .Where((_, idx) => !(idx == 1 || idx == 2)) .ToArray());
var aRow = “a, b, c, d, e, f”;
var rowWithColumnsRemoved = aRow
.Split(‘,’) .Where((_, idx) => !(idx == 1 || idx == 2)) .ToArray());
var newRow = String.Join(“,”, rowWithColumnsRemoved);
Checking if any parameters are null
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 params = new List<string> {p1, p2, p3}; if (params.Any(p => p == null) throw new Exception(...);
// rest of constructor logic }}
Some lessons from the wild
Dealing with the null collection
public int SumNumbers(List<int> list){if(list == null)
return 0;return list.Sum(v => v);
}
public IEnumerable<T> EmptyIfNull(this IEnumerable<T> collection)
{if(collection == null)
return new List<T>();return collection;
}
Chris Ammerman in the comments sectionhttp://www.markhneedham.com/blog/2009/06/16/functional-collection-
parameters-handling-the-null-collection/
public int SumNumbers(List<int> list){
return list.EmptyIfNull().Sum(v => v);
}
LINQ and the forgotten abstraction
Avoid passing lists around
public class Quote{public List<Coverage> Coverages {
get; set; }
}
Later on in the view…
<% var coverages = Model.Quote.Coverages; %>
<% if(coverages.Where(c => c.Name == “coverageType1”) %>
...
<% if(coverages.Where(c => c.Name == “coverageType2”) %>
...
<% if(coverages.Where(c => c.Name == “coverageType3”) %>
Objects as the mechanism for encapsulation
public class Coverages{
private readonly List<Coverage> coverages;
public Coverages(List<Coverage> coverages){
this.coverages = new List<Coverage>(coverages);}
public Coverage CoverageType1{ get {
return coverages.Where(c => c.Name == “coverageType1”;
}}
}
LINQ is duplication too!
public class Coverages{
public Coverage CoverageType1{ get {
return coverages.Where(c => c.Name == “coverageType1”;
}}
public Coverage CoverageType2{ get {
return coverages.Where(c => c.Name == “coverageType2”;
}}
}
public class Coverages{
public Coverage CoverageType1{
get { return CoverageByName(“coverageType1”); }
}
public Coverage CoverageType2{
get { return CoverageByName(“coverageType2”); }
}
public Coverage CoverageBy(string name){
return coverages.Where(c => c.Name == name);}
}
Don’t forget “extract method”
var someFoos = new List<Foo>()// put some foos in the list
someFoos.Select(f => new NewFoo {
Property1 = f.Property1...
});
var someFoos = new List<Foo>()// put some foos in the list
someFoos.Select(f => CreateNewFooFrom(f));
Naming lambda variables
Putting functions in maps
public void SomeMethodParsing(string input){
if (input == “input1”) {
someMethod(input);}else if (input == “input2”){
someOtherMethod(input);}// and so on
}
Dictionary<string, Action<string>> inputs = new Dictionary<string, Action<string>> { { “input1” , someMethod }, { “input2” , someOtherMethod } };
public void SomeMethodParsing(string input){
var method = inputs[input];method(input);
}
Large
Medium
Small
Using functions to simplify some GOF patterns
Action
Func
public class SomeObject{private readonly IStrategy strategy;
public Foo(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 Foo(Action<string> strategy){
this. strategy = strategy;}
public void DoSomething(string value){
strategy(value);}
}
Need to ensure we don’t lose readability/understandability
The hole in the middle pattern
Brian Hurthttp://enfranchisedmind.com/blog/posts/the-hole-in-the-middle-pattern/
Common beginning and end.Only the middle differs
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);
}}
Other ideas I’m intrigued about
The option type
The nested closure
File.open(“someFile.txt”, ‘w’) do |out|out << “add something to file”
end
public class MyFile { public static void Open(string filePath,
Action<StreamWriter> block) { using(var writer = new StreamWriter()) { block(writer ); } }}
MyFile.Open(“c:\\mark.txt”, f => f.WriteLine(“some random text”));
Writing extension methods to create functional abstractions
Learning more
QuickTime™ and a decompressor
are needed to see this picture.
3 things to take away
The transformational mindset
Objects are still the mechanism for encapsulation
Simplify GOF design patterns by using functions
Thanks to…
• Dave Cameron (not that one!) who I worked with on the structure and general content of the talk.
• Mike Wagg, Brian Blignaut, Chris Owen for reviewing the talk and giving their input.