Click here to Skip to main content
14,271,512 members

Functional Programming In Object-Oriented Programming In C#

Rate this:
3.43 (13 votes)
Please Sign up or sign in to vote.
3.43 (13 votes)
22 Jul 2019CPOL
This article discusses the idea of using functional programming to enrich object-oriented programming in C#.

Table Of Contents

  1. Introduction
  2. Functional Programming Techniques Emulated In Object-Oriented Programming
  3. Granularity Mismatch
  4. An Object-Oriented Functional Programming Construct
  5. An Interrelation Functional Programming/Object-Oriented Programming
  6. Functional Programming Integration In C#
  7. Code Abstraction At A Function Level
  8. Operation Composition
  9. Function Partial Applications And Currying
  10. Architectural Functional Programming Techniques In Object-Oriented Programming
  11. Some Classic Object-Oriented Design Patterns With Functional Programming
    1. Strategy
    2. Command
    3. Observer
    4. Virtual Proxy
    5. Visitor
  12. Summing Up
  13. History

Introduction

Image 1

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
  • 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 delegates:

delegate string StringFunType(string s); // declaration

string G1(string s){ // a method whose type matches StringFunType
  return "some string" + s;
}

StringFunType f1;    // declaration of a delegate variable
f1 = G1;             // direct method value assignment
f1("some string");   // application of the delegate variable

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

string Gf1(StringFunType f, string s){ [ ... ] } // delegate f as a parameter
Console.WriteLine(Gf1(G1, "Boo"));   // call

Delegates 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(){ // delegate as a return value
  [ ... ]
  return (new SomeClass()).G;
}

Console.WriteLine(Gf2()("Boo")); // call

Delegates can take place into data structures:

var l = new LinkedList<StringFunType>(); // list of delegates
[ ... ]
l.AddFirst(G1) ;                         // insertion of a delegate in the list
Console.WriteLine(l.First.Value("Boo")); // extract and call

C# delegates may be anonymous:

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

Anonymous delegates 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(); // usable as a String method
SimpleWordCount(s1);  // also usable as a standalone method

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 delegates. 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 ... ]; // some code using x1, x2 and y
  [ ... ]
}

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

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) ;        // partial application

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); // partial application

Architectural Functional Programming Techniques In Object-Oriented Programming

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

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

Some Classic Object-Oriented Design Patterns With Functional Programming

Image 2

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.

Image 3

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

Image 4

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

Image 5

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

Image 6

Observer

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

Image 7

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.

Image 8

Virtual Proxy

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

Image 9

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.

Image 10

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 Visitors:

  • Refactoring Resistance: A Visitor 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).
  • Invasiveness: A Visitor 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(); // no Accept method required
}
[ ... ]

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(); // dynamic dispatch required...
    [ ... ]
  }
}

Through functional programming, Visitors 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);
  }
}

Image 11

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

  • 21st July, 2019: Initial version
  • 23rd July, 2019: Updated design patterns, Strategy, Observer, Virtual Proxy, Visitor and Conclusion sections

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Akram El Assas
Architect
Morocco Morocco
Akram El Assas graduated from the french engineering school ENSEIRB located in Bordeaux, a city in the south of France, and got his diploma in software engineering in 2010. He worked in France for Mediatvcom, a company specialized in audiovisual, digital television and new technologies. Mediatvcom offers services such as consulting, project management, audit and turnkey solutions adapted to the needs of customers. Akram worked mainly with Microsoft technologies such as C#, ASP.NET and SQL Server but also with JavaScript, jQuery, HTML5 and CSS3. Akram worked on different projects around digital medias such as Media Asset Management systems, Digital Asset Management systems and sometimes on HbbTV apps.

Comments and Discussions

 
PraiseGood info... Pin
Member 85002330-Jul-19 3:25
memberMember 85002330-Jul-19 3:25 
GeneralMy vote of 3 Pin
BillWoodruff24-Jul-19 12:38
mveBillWoodruff24-Jul-19 12:38 
QuestionBut Why? Pin
#realJSOP22-Jul-19 5:08
mve#realJSOP22-Jul-19 5:08 
AnswerRe: But Why? Pin
Akram El Assas22-Jul-19 7:49
memberAkram El Assas22-Jul-19 7:49 
AnswerRe: But Why? Pin
BillWoodruff24-Jul-19 12:43
mveBillWoodruff24-Jul-19 12:43 
AnswerRe: But Why? Pin
Kirk Wood24-Jul-19 14:23
memberKirk Wood24-Jul-19 14:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Article
Posted 21 Jul 2019

Tagged as

Stats

15.1K views
138 downloads
24 bookmarked