19 min read

In this article by Wisnu Anggoro, the author of the book, Functional C#, we are going to explore the functional programming by testing it. We will use the power of C# to construct some functional code. We will also deal with the features in C#, which are mostly used in developing functional programs. By the end of this chapter, we will have an idea how the functional approach in C# will be like. Here are the topics we will cover in this chapter:

  • Introduction to functional programming concept
  • Comparing between the functional and imperative approach
  • The concepts of functional programming
  • The advantages and disadvantages of functional programming

(For more resources related to this topic, see here.)

In functional programming, we write functions without side effects the way we write in Mathematics. The variable in the code function represents the value of the function parameter, and it is similar to the mathematical function. The idea is that a programmer defines the functions that contain the expression, definition, and the parameters that can be expressed by a variable in order to solve problems.

After a programmer builds the function and sends the function to the computer, it’s the computer’s turn to do its job. In general, the role of the computer is to evaluate the expression in the function and return the result. We can imagine that the computer acts like a calculator since it will analyze the expression from the function and yield the result to the user in a printed format. The calculator will evaluate a function which are composed of variables passed as parameters and expressions which forms the body of the function. Variables are substituted by its value in the expression. We can give simple expression and compound expressions using Algebraic operators. Since expression without assignments never alter the value, sub expressions needs to be evaluated only once.

Suppose we have the expression 3 + 5 inside a function. The computer will definitely return 8 as the result right after it completely evaluates it. However, this is just a simple example of how the computer acts in evaluating an expression. In fact, a programmer can increase the ability of the computer by creating a complex definition and expression inside the function. Not only can the computer evaluate the simple expression, but it can also evaluate the complex calculation and expression.

Understanding definitions, scripts, and sessions

As we discuss earlier about  a calculator that will analyze the expression from the function, let’s imagine we have a calculator that has a console panel like a computer does. The difference between that and a conventional calculator is that we have to press Enter instead of = (equal to) in order to run the evaluation process of the expression. Here, we can type the expression and then press Enter. Now, imagine that we type the following expression:

3 x 9

Immediately after pressing Enter, the computer will print 27 in the console, and that’s what we are expecting. The computer has done a great job of evaluating the expression we gave. Now, let’s move to analyzing the following definitions. Imagine that we type them on our functional calculator:

square a = a * a
max a b  = a, if a ≥ b
         = b, if b > a

We have defined the two definitions, square and max. We can call the list of definitions script. By calling the square function followed by any number representing variable a, we will be given the square of that number. Also, in the max definition, we serve two numbers to represent variable a and b, and then the computer will evaluate this expression to find out the biggest number between the variables.

By defining these two definitions, we can use them as a function, which we can call session, as follows:

square (1 + 2)

The computer will definitely print 9 after evaluating the preceding function. The computer will also be able to evaluate the following function:

max 1 2

It will return 2 as the result based on the definition we defined earlier. This is also possible if we provide the following expression:

square (max 2 5)

Then, 25 will be displayed in our calculator console panel.

We can also modify a definition using the previous definition. Suppose we want to quadruple an integer number and take advantage of the definition of the square function; here is what we can send to our calculator:

quad q = square q * square a
quad 10

The first line of the preceding expression is a definition of the quad function. In the second line, we call that function, and we will be provided with 10000 as the result.

The script can define the variable value; for instance, take a look at the following:

radius = 20

So, we should expect the computer to be able to evaluate the following definition:

area = (22 / 7) * square (radius)

Understanding the functions for functional programming

Functional programming uses a technique of emphasizing functions and their application instead of commands and their execution. Most values in functional programming are function values. Let’s take a look at the following mathematical notation:

f :: A -> B

From the preceding notation, we can say that function f is a relation of each element stated there, which is A and B. We call A, the source type, and B, the target type. In other words, the notation of A à B states that A is an argument where we have to input the value, and B is a return value or the output of the function evaluation.

Consider that x denotes an element of A and x + 2 denotes an element of B, so we can create the mathematical notation as follows:

f(x) = x + 2

In mathematics, we use f(x) to denote a functional application. In functional programming, the function will be passed an argument and will return the result after the evaluation of the expression.

We can construct many definitions for one and the same function. The following two definitions are similar and will triple the input passed as an argument:

triple y = y + y + y
triple' y = 3 * y

As we can see, triple and triple’ have different expressions. However, they are the same functions, so we can say that triple = triple’. Although we have many definitions to express one function, we will find that there is only one definition that will prove to be the most efficient in the procedure of evaluation in the sense of the reducing the expression we discussed previously. Unfortunately, we cannot determine which one is the most efficient from our preceding two definitions since that depends on the characteristic of the evaluation mechanism.

Forming the definition

Now, let’s go back to our discussion on definitions at the beginning of this chapter. We have the following definition in order to retrieve the value from the case analysis:

max a b  = a, if a ≥ b
         = b, if b > a

There are two expressions in this definition, distinguished by a Boolean-value expression. This distinguisher is called guards, and we use them to evaluate the value of True or False. The first line is one of the alternative result values for this function. It states that the return value will be a if the expression a ≥ b is True. In contrast, the function will return value b if the expression b ≥ a is True. Using these two cases, a ≥ b and b ≥ a, the max value depends on the value of a and b. The order of the cases doesn’t matter. We can also define the max function using the special word otherwise. This word ensures that the otherwise case will be executed if no expression results in a True value. Here, we will refactor our max function using the word otherwise:

max a b  = a, if a ≥ b
         = b, otherwise

From the preceding function definition, we can see that if the first expression is False, the function will return b immediately without performing any evaluation. In other words, the otherwise case will always return True if all previous guards return False.

Another special word usually used in mathematical notations is where. This word is used to set the local definition for the expression of the function. Let’s take a look at the following example:

f x y = (z + 2) * (z + 3)
        where z = x + y

In the preceding example, we have a function f with variable z, whose value is determined by x and y. There, we introduce a local z definition to the function. This local definition can also be used along with the case analysis we have discussed earlier. Here is an example of the conjunction local definition with the case analysis:

f x y = x + z, if x > 100
      = x - z, otherwise
        where z = triple(y + 3)

In the preceding function, there is a local z definition, which qualifies for both x + z and x – z expressions. As we discussed earlier, although the function has two equal to (=) signs, only one expression will return the value.

Currying

Currying is a simple technique of changing structure arguments by sequence. It will transform a n-ary function into n unary function. It is a technique which was created to circumvent limitations of Lambda functions which are unary functions Let’s go back to our max function again and get the following definition:

max a b  = a, if a ≥ b
         = b, if b > a

We can see that there is no bracket in the max a b function name. Also, there is no comma-separated a and b in the function name. We can add a bracket and a comma to the function definition, as follows:

max' (a,b)  = a, if a ≥ b
            = b, if b > a

At first glance, we find the two functions to be the same since they have the same expression. However, they are different because of their different type. The max’ function has a single argument, which consists of a pair of numbers. The type of max’ function can be written as follows:

max' :: (num, num) -> num

On the other hand, the max function has two arguments. The type of this function can be written as follows:

max :: num -> (num -> num)

The max function will take a number and then return a function from single number to many numbers. From the preceding max function, we pass the variable a to the max function, which returns a value. Then, that value is compared to variable b in order to find the maximum number.

Comparison between functional and imperative programming

The main difference between functional and imperative programming is that imperative programming produces side-effects while functional programming doesn’t. In Imperative programming, the expressions are evaluated and its resulting value is assigned to variables. So, when we group series of expressions into a function, the resulting value depends upon the state of variables at that point in time. This is called side effects. Because of the continues change in state, the order of evaluation matter. In Functional programming world, destructive assignment is forbidden and each time an assignment happens a new variable is induced.

Concepts of functional programming

We can also distinguish functional programming over imperative programming by the concepts. The core ideas of Functional programming are encapsulated in the constructs like First Class Functions, Higher Order Functions, Purity, Recursion over Loops, and Partial Functions. We will discuss the concepts in this topic.

First-class and higher-order functions

In Imperative programming, the given data is more importance and are passed through series of functions (with side effects). Functions are special constructs with its own semantics. In effect, functions do not have the same place as variables and constants. Since a function cannot be passed as parameter or not returned as a result, they are regarded as second class citizens of the programming world. In the functional programming world, we can pass function as a parameter and return function as a result. They obey the same semantics as variables and their values. Thus, they are First Class Citizens. We can also create function of functions called Second Order Function through Composition. There is no limit imposed on the composability of function and they are called Higher Order Functions.

Fortunately, the C# language has supported these two concepts since it has a feature called function object, which has types and values. To discuss more details about the function object, let’s take a look at the following code:

class Program
{
  static void Main(string[] args)
  {
    Func<int, int> f = (x) => x + 2;
    int i = f(1);
    Console.WriteLine(i);

    f = (x) => 2 * x + 1;
    i = f(1);
    Console.WriteLine(i);
  }
}

We can find the code in FuncObject.csproj, and if we run it, it will display the following output on the console screen:

Why do we display it? Let’s continue the discussion on function types and function values.

Hit Ctrl + F5 instead of F5 in order to run the code in debug mode but without the debugger. It’s useful to stop the console from closing on the exit.

Pure functions

In the functional programming, most of the functions do not have side-effects. In other words, the function doesn’t change any variables outside the function itself. Also, it is consistent, which means that it always returns the same value for the same input data. The following are example actions that will generate side-effects in programming:

  • Modifying a global variable or static variable since it will make a function interact with the outside world.
  • Modifying the argument in a function. This usually happens if we pass a parameter as a reference.
  • Raising an exception.
  • Taking input and output outside—for instance, get a keystroke from the keyboard or write data to the screen.

    Although it does not satisfy the rule of a pure function, we will use many Console.WriteLine() methods in our program in order to ease our understanding in the code sample.

The following is the sample non-pure function that we can find in NonPureFunction1.csproj:

class Program
{
  private static string strValue = "First";

  public static void AddSpace(string str)
  {
    strValue += ' ' + str;
  }

  static void Main(string[] args)
  {
    AddSpace("Second");
    AddSpace("Third");
    Console.WriteLine(strValue);
  }
}

If we run the preceding code, as expected, the following result will be displayed on the console:

In this code, we modify the strValue global variable inside the AddSpace function. Since it modifies the variable outside, it’s not considered a pure function.

Let’s take a look at another non-pure function example in NonPureFunction2.csproj:

class Program
{
  public static void AddSpace(StringBuilder sb, string str)
  {
    sb.Append(' ' + str);
  }

  static void Main(string[] args)
  {
    StringBuilder sb1 = new StringBuilder("First");
    AddSpace(sb1, "Second");
    AddSpace(sb1, "Third");
    Console.WriteLine(sb1);
  }
}

We see the AddSpace function again but this time with the addition of an argument-typed StringBuilder argument. In the function, we modify the sb argument with hyphen and str. Since we pass the sb variable by reference, it also modifies the sb1 variable in the Main function. Note that it will display the same output as NonPureFunction2.csproj.

To convert the preceding two non-pure function code into pure function code, we can refactor the code to be the following. This code can be found at PureFunction.csproj:

class Program
{
  public static string AddSpace(string strSource, string str)
  {
    return (strSource + ' ' + str);
  }

  static void Main(string[] args)
  {
    string str1 = "First";
    string str2 = AddSpace(str1, "Second");
    string str3 = AddSpace(str2, "Third");
    Console.WriteLine(str3);
  }
}

Running PureFunction.csproj, we will get the same output compared to the two previous non-pure function code. However, in this pure function code, we have three variables in the Main function. This is because in functional programming, we cannot modify the variable we have initialized earlier. In the AddSpace function, instead of modifying the global variable or argument, it now returns a string value to satisfy the the functional rule.

The following are the advantages we will have if we implement the pure function in our code:

  • Our code will be easy to be read and maintain because the function does not depend on external state and variables. It is also designed to perform specific tasks that increase maintainability.
  • The design will be easier to be changed since it is easier to refactor.
  • Testing and debugging will be easier since it’s quite easy to isolate the pure function.

Recursive functions

In imperative programming world, we have got destructive assignment to mutate the state if a variable. By using loops, one can change multiple variables to achieve the computational objective. In Functional programming world, since variable cannot be destructively assigned, we need a Recursive function calls to achieve the objective of looping.

Let’s create a factorial function. In mathematical terms, the factorial of the nonnegative integer N is the multiplication of all positive integers less than or equal to N. This is usually denoted by N!. We can denote the factorial of 7 as follows:

7! = 7 x 6 x 5 x 4 x 3 x 2 x 1
   = 5040

If we look deeper at the preceding formula, we will discover that the pattern of the formula is as follows:

N! = N * (N-1) * (N-2) * (N-3) * (N-4) * (N-5) ...

Now, let’s take a look at the following factorial function in C#. It’s an imperative approach and can be found in the RecursiveImperative.csproj file.

public partial class Program
{
  private static int GetFactorial(int intNumber)
  {
    if (intNumber == 0)
    {
      return 1;
    }

    return intNumber * GetFactorial(intNumber - 1);
  }
}

As we can see, we invoke the GetFactorial() function from the GetFactorial() function itself. This is what we call a recursive function. We can use this function by creating a Main() method containing the following code:

public partial class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(
      "Enter an integer number (Imperative approach)");
    int inputNumber = Convert.ToInt32(Console.ReadLine());
    int factorialNumber = GetFactorial(inputNumber);
    Console.WriteLine(
      "{0}! is {1}",
      inputNumber,
      factorialNumber);
  }
}

We invoke the GetFactorial() method and pass our desired number to the argument. The method will then multiply our number with what’s returned by the GetFactorial() method, in which the argument has been subtracted by 1. The iteration will last until intNumber – 1 is equal to 0, which will return 1.

Now, let’s compare the preceding recursive function in the imperative approach with one in the functional approach. We will use the power of the Aggregate operator in the LINQ feature to achieve this goal. We can find the code in the RecursiveFunctional.csproj file. The code will look like what is shown in the following:

class Program

{

  static void Main(string[] args)

  {

    Console.WriteLine(

      "Enter an integer number (Functional approach)");

    int inputNumber = Convert.ToInt32(Console.ReadLine());

    IEnumerable<int> ints = Enumerable.Range(1, inputNumber);

    int factorialNumber = ints.Aggregate((f, s) => f * s);

    Console.WriteLine(

      "{0}! is {1}",

      inputNumber,

      factorialNumber);

  }

}

We initialize the ints variable, which contains a value from 1 to our desired integer number in the preceding code, and then we iterate ints using the Aggregate operator. The output of RecursiveFunctional.csproj will be completely the same compared to the output of RecursiveImperative.csproj. However, we use the functional approach in the code in RecursiveFunctional.csproj.

The advantages and disadvantages of functional programming

So far, we have had to deal with functional programming by creating code using functional approach. Now, we can look at the advantages of the functional approach, such as the following:

  • The order of execution doesn’t matter since it is handled by the system to compute the value we have given rather than the one defined by programmer. In other words, the declarative of the expressions will become unique. Because functional programs have an approach toward mathematical concepts, the system will designed with the notation as close as possible to the mathematical way of concept.
  • Variables can be replaced by their value since the evaluation of expression can be done any time. The functional code is then more mathematically traceable because the program is allowed to be manipulated or transformed by substituting equals with equals. This feature is called Referential Transparency.
  • Immutability makes the functional code free of side-effects. A shared variable, which is an example of a side-effect, is a serious obstacle for creating parallel code and result in non-deterministic execution. By removing the side-effect, we can have a good coding approach.
  • The power of lazy evaluation will make the program run faster because it only provides what we really required for the queries result. Suppose we have a large amount of data and want to filter it by a specific condition, such as showing only the data that contains the word Name. In imperative programming, we will have to evaluate each operation of all the data. The problem is when the operation takes a long time, the program will need more time to run as well. Fortunately, the functional programming that applies LINQ will perform the filtering operation only when it is needed. That’s why functional programming will save much of our time using lazy evaluation.
  • We have a solution for complex problems using composability. It is a rule principle that manages a problem by dividing it, and it gives pieces of the problem to several functions. The concept is similar to a situation when we organize an event and ask different people to take up a particular responsibility. By doing this, we can ensure that everything will done properly by each person.

Beside the advantages of functional programming, there are several disadvantages as well. Here are some of them:

  • Since there’s no state and no update of variables is allowed, loss of performance will take place. The problem occurs when we deal with a large data structure and it needs to perform a duplication of any data even though it only changes a small part of the data.
  • Compared to imperative programming, much garbage will be generated in functional programming due to the concept of immutability, which needs more variables to handle specific assignments. Because we cannot control the garbage collection, the performance will decrease as well.

Summary

So we have been acquainted with the functional approach by discussing the introduction of functional programming. We also have compared the functional approach to the mathematical concept when we create functional program. It’s now clear that the functional approach uses the mathematical approach to compose a functional program.

The comparison between functional and imperative programming also led us to the important point of distinguishing the two. It’s now clear that in functional programming, the programmer focuses on the kind of desired information and the kind of required transformation, while in the imperative approach, the programmer focuses on the way of performing the task and tracking changes in the state.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here