This article discusses the idea of using functional programming to enrich object-oriented programming in C#.

Please Sign up or sign in to vote.

Functional Programming In Object-Oriented Programming In C#

Table Of Contents

Introduction

The idea of using functional programming to enrich object-oriented programming is old. Adding functional programming capabilities to an object-oriented language leads to benefits in object-oriented programming design.

Some old and less old languages with functional programming and object-oriented programming:

For instance, Smalltalk and Common Lisp

And more recently Python or Ruby

Functional Programming Techniques Emulated In Object-Oriented Programming

Practices in object-oriented programming languages include emulations of functional programming techniques:

C++: Function pointers and the overloading of the () operator

operator Java: Anonymous classes and reflexion

Granularity Mismatch

Functional programming and object-oriented programming operate on different design granularity levels:

Functions/methods: Programming in the small level

Classes/objects/modules: Programming in the large level

There are at least two questions:

Where do we locate the source of individual functions in an object-oriented programming architecture?

How do we relate such individual functions to an object-oriented programming architecture?

An Object-Oriented Functional Programming Construct

C# offers a function programming feature called delegate s:

delegate string StringFunType( string s); string G1( string s){ return " some string" + s; } StringFunType f1; f1 = G1; f1( " some string" );

Delegate s are first-class values. This means that delegate types can type method parameters, and delegate s can be passed as arguments as any other values:

string Gf1(StringFunType f, string s){ [ ... ] } Console.WriteLine(Gf1(G1, " Boo" ));

Delegate s can be returned as a computation of a method. For instance, assuming G is a method of type string => string and implemented in SomeClass :

StringFunType Gf2(){ [ ... ] return ( new SomeClass()).G; } Console.WriteLine(Gf2()( " Boo" ));

Delegate s can take place into data structures:

var l = new LinkedList<StringFunType>(); [ ... ] l.AddFirst(G1) ; Console.WriteLine(l.First.Value( " Boo" ));

C# delegate s may be anonymous:

delegate ( string s){ return s + " some string" ; };

Anonymous delegate s can look even more like lambda expressions:

s = > { return s + " some string" ; }; s = > s + " some string" ;

An Interrelation Functional Programming/Object-Oriented Programming

Extension methods enable a programmer to add methods to existing classes without creating new derived classes:

static int SimpleWordCount( this string str){ return str.Split( new char []{ ' ' }).Length; } string s1 = " some chain" ; s1.SimpleWordCount(); SimpleWordCount(s1);

Another example of extension methods:

static IEnumerable<T> MySort<T>( this IEnumerable<T> obj) where T:IComparable<T>{ [ ... ] } List<int> someList = [ ... ]; someList.MySort();

Extension methods have harsh constraints in C#:

Only static

Not polymorphic

Functional Programming Integration In C#

C# offers functional and procedural generic delegate predefined types for arity up to 16:

delegate TResult Func<TResult>(); delegate TResult Func<T, TResult>(T a1); delegate TResult Func<T1, T2, TResult>(T1 a1, T2 a2); delegate void Action<T>(T a1); [ ... ]

A delegate may itself contain an invocation list of delegate s. When such delegate is called, methods included in the delegate are invoked in the order in which they appear in the list. The result value is determined by the last method called in the list.

C# allows lambda expressions to be represented as data structures called expression trees:

Expression<Func<int, int>> expression = x = > x + 1 ; var d = expression.Compile(); d.Invoke( 2 );

As such, they may be stored and transmitted.

Code Abstraction at a Function Level

A simple code:

float M( int y){ int x1 = [ ... ]; int x2 = [ ... ]; [ ... ] [ ... some code ... ]; [ ... ] }

With functional abstraction:

public delegate int Fun( int x, int y, int z); float MFun(Fun f, int x2, int y){ int x1 = [ ... ]; [ ... ] f(x1, x2, y); [ ... ] } int z1 = MFun(F1, 1 , 2 ); int z2 = MFun(F2, 1 , 2 );

The advantages of functional abstraction is that there are no local duplications and there is separation of concerns.

A simple and effective application of the functional abstraction is generic higher-order iterated operations over data.

For instance, the internal iterators ( Map s):

IEnumerable<T2> Map<T1, T2>( this IEnumerable<T1> data, Func<T1, T2> f){ foreach ( var x in data) yield return f(x); } someList.Map(i = > i * i);

Operation Composition

In functional programming, operation compositions are easy. An initial code:

public static void PrintWordCount( string s){ string [] words = s.Split( ' ' ); for ( int i = 0 ; i < words.Length; i++) words[i] = words[i].ToLower(); var dict = new Dictionary<string, int>(); foreach ( var word in words) if (dict.ContainsKey(word)) dict[word]++; else dict.Add(word, 1 ); foreach ( var x in dict) Console.WriteLine( " {0}: {1}" , x.Key, x.Value.ToString()); }

A first factoring using higher-order functions:

public static void PrintWordCount( string s){ string [] words = s.Split( ' ' ); string [] words2 = ( string []) Map(words, w = > w.ToLower()); Dictionary<string, int> res = (Dictionary<string, int>) Count(words2); App(res, x = > Console.WriteLine( " {0}: {1}" , x.Key, x.Value.ToString())); }

A second factoring using extension methods:

public static void PrintWordCount( string s){ s .Split( ' ' ) .Map(w = > w.ToLower()) .Count() .App(x = > Console.WriteLine( " {0}: {1}" , x.Key, x.Value.ToString())); }

We can see that the readability of the code increased.

In C#, such operation compositions are often used with LINQ which is defined to unify programming with relational data or XML. Below is a simple example using LINQ:

var q = programmers .Where(p = > p.Age > 20 ) .OrderByDescending(p = > p.Age) .GroupBy(p = > p.Language) .Select(g = > new { Language = g.Key, Size = g.Count(), Names = g });

Function Partial Applications And Currying

With first-class functions, every n-ary function can be transformed into a composition of n unary functions, that is, into a curried function:

Func<int, int , int> lam1 = (x, y) = > x + y; Func<int, Func<int, int>> lam2 = x = > (y = > x + y); Func<int, int> lam3 = lam2( 3 ) ;

Currying:

public static Func<T1, Func<T2, TRes>> Curry<T1, T2, TRes>( this Func<T1, T2, TRes> f){ return (x = > (y = > f(x, y))); } Func<int, int> lam4 = lam1.Curry()( 3 );

Architectural Functional Programming Techniques In Object-Oriented Programming

Some architectural effects of having functional programming capabilities in object-oriented programming:

Reduction of the number of object/class definitions Name abstraction at a function/method level Operation compositions (and sequence comprehensions) Function partial applications and currying

Some Classic Object-Oriented Design Patterns With Functional Programming

Why is functional programming usually integrated to object-oriented programming?

Principal object-oriented programming languages are based on classes as modules: C#, C++, Java.

One of the strong ideas of development in object-oriented programming: maintenance, extension and adaptation actions can go through inheritance and class composition. (This avoids any modification of the existing code.) Functional programming is a solution to this problem.

For example, the Strategy design pattern.

Strategy

A Strategy pattern lets an algorithm vary independently of clients that use it.

A Strategy: just a case of abstracting code at a method level (No need of object-oriented encapsulation and new class hierarchies). For instance, in the .NET Framework:

public delegate int Comparison<T>(T x, T y); public void Sort(Comparison<T> comparison); public delegate bool Predicate<T>(T obj); public List<T> FindAll(Predicate<T> match);

Other design patterns such as Command, Observer, Visitor and Virtual Proxy can beneficiate of first-class functions:

Command

The Command pattern encapsulates requests (method calls) as objects so that they can easily be transmitted, stored, and applied. For instance, menu implementations:

public delegate void EventHandler( object sender, EventArgs e); public event EventHandler Click; private void menuItem1_Click( object sender, EventArgs e){ OpenFileDialog fd = new OpenFileDialog(); fd.DefaultExt = " *.*" ; fd.ShowDialog(); } public void CreateMyMenu(){ MainMenu mainMenu1 = new MainMenu(); MenuItem menuItem1 = new MenuItem(); [ ... ] menuItem1.Click += new EventHandler(menuItem1_Click); }

Observer

A one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated.

Below is a classic implementation of the observer design pattern:

public interface Observer<S>{ void Update(S s); } public abstract class Subject<S>{ private List<Observer<S>> _observ = new List<Observer<S>>(); public void Attach(Observer<S> obs){ _observ.Add(obs); } public void Notify(S s){ foreach ( var obs in _observ) obs.Update(s); } }

With functional programming:

public delegate void UpdateFun<S>(S s); public abstract class Subject<S>{ private UpdateFun<S> _updateHandler; public void Attach(UpdateFun<S> f){ _updateHandler += f; } public void Notify(S s){ _updateHandler(s); } }

We can see that there is no need of observer classes with methods called Update .

Virtual Proxy

The Virtual Proxy pattern: placeholders for other objects such that their data are created/computed only when needed.

Below is a classic implementation of the virtual proxy design pattern:

public class SimpleProxy : I{ private Simple _simple; private int _arg; protected Simple GetSimple(){ if (_simple == null ) _simple = new Simple(_arg); return _simple; } public SimpleProxy( int i){ _arg = i ; } public void Process(){ GetSimple().Process(); } }

Below is the implementation of the virtual proxy design pattern using functional programming and laziness:

public class SimpleLazyProxy : I{ private Lazy<Simple> _simpleLazy; public SimpleLazyProxy( int i){ _simpleLazy = new Lazy<Simple>(() = > new Simple(i)); } public void Process(){ _simpleLazy.Value.Process(); } }

Visitor

The Visitor pattern lets you define new operations without changing the classes of the elements on which they operate. Without Visitors, each subclass of a hierarchy has to be edited or derived separately. Visitors are at the crux of many of the programming design problems.

Below is a classic implementation of the Visitor design pattern:

public interface IFigure{ string GetName(); void Accept<T>(IFigureVisitor<T> v); } public class SimpleFigure : IFigure{ private string _name; public SimpleFigure( string name){ _name = name; } public string GetName(){ return _name; } public void Accept<T>(IFigureVisitor<T> v){ v.Visit( this ); } } public class CompositeFigure : IFigure{ private string _name; private IFigure[] _figureArray; public CompositeFigure( string name, IFigure[] s){ _name = name; _figureArray = s; } public string GetName(){ return _name; } public void Accept<T>(IFigureVisitor<T> v){ foreach (IFigure f in _figureArray) f.Accept (v); v.Visit( this ); } } public interface IFigureVisitor<T>{ T GetVisitorState(); void Visit(SimpleFigure f); void Visit(CompositeFigure f); } public class NameFigureVisitor : IFigureVisitor<string>{ private string _fullName = " " ; public string GetVisitorState(){ return _fullName; } public void Visit(SimpleFigure f){ _fullName += f.GetName() + " " ; } public void Visit(CompositeFigure f){ _fullName += f.GetName() + " /" ; } }

Some well-known weaknesses of Visitor s:

Refactoring Resistance : A Visitor definition is dependent on the set of client classes on which it operates.

: A definition is dependent on the set of client classes on which it operates. Staticness : A Visitor is static in its implementation (type-safety but less flexibility).

: A is in its implementation (type-safety but less flexibility). Invasiveness : A Visitor needs that the client classes anticipate and/or participate in making the selection of the right method.

: A needs that the client classes anticipate and/or participate in making the selection of the right method. Naming Inflexibility: A Visitor needs that all the different implementations of the visit methods be similarly named.

An attempt to solve Visitor problems with extension methods:

public interface IFigure{ string GetName(); } [ ... ] public static class NameFigureVisitor{ public static void NameVisit( this SimpleFigure f){ _state = f.GetName() + " " + _state; } public static void NameVisit( this CompositeFigure f) { _fullName = f.GetName() + " :" + _fullName; foreach (IFigure g in f.GetFigureArray()) g.NameVisit(); [ ... ] } }

Through functional programming, Visitor s can be functions:

public delegate T VisitorFun<V, T>(V f); public interface IFigureF{ string GetName (); T Accept<T>(VisitorFun<IFigureF, T> v); } public class SimpleFigureF : IFigureF{ private string _name ; public SimpleFigureF( string name){ _name = name ; } public string GetName(){ return _name ; } public T Accept<T>(VisitorFun<IFigureF, T> v){ return v( this ); } } [...] public class CompositeFigureF : IFigureF{ private string _name; private IFigureF[ ] _figureArray; public CompositeFigureF( string name, IFigureF[] s){ _name = name; _figureArray = s; } public string GetName(){ return this ._name; } public T Accept<T>(VisitorFun<IFigureF, T> v){ foreach (IFigureF f in _figureArray) f.Accept(v); return v( this ); } }

Below is a simple functional Visitor :

public static VisitorFun<IFigureF, string> MakeNameFigureVisitorFun(){ string _state = " " ; return obj = > { if (obj is SimpleFigureF) _state += obj.GetName() + " " ; else if (obj is CompositeFigureF) _state += obj.GetName() + " /" ; return _state ; }; }

Below is a data-driven oriented Visitor :

var dict1 = new Dictionary<Type, VisitorFun<IFigureF, string>>(); dict1.Add( typeof (SimpleFigureF), f = > f.GetName() + " " ); dict1.Add( typeof (CompositeFigureF), f = > f.GetName() + " /" ); var nameFigureFunVisitor1 = MakeVisitorFun<IFigureF, string>(dict1);

We can see that with functional programming and data-driven programming, there is less refactoring resistance, less name rigidity and less staticness.

Summing Up

Object-orientend programming with functional programming granularity level:

Functional programming is good for modular-objects

Code Abstraction at a function/method level

Convenient generic iterator/loop implementations

Operation compositions, sequence/query comprehensions

Function partial applications

Limitations of the number of object/class definitions

Name abstractions at a function/method level

Laziness emulations (used in Virtual Proxies)

Data-driven programming (used in Visitors)

Architecture simplifications

Increased flexibility

Adding functional programming capabilities to an object-oriented language leads to benefits in object-oriented programming design.

History