C # is almost functional

Original author: Enrico Buonanno
  • Transfer
Hello dear readers! Our research in the field of C # language seriously echoes this article, the author of which is a specialist in functional programming in C #. The article is an excerpt from the forthcoming book, so at the end of the post we suggest voting for this book.



Many programmers implicitly imply that "functional programming (FP) should only be implemented in a functional language." C # is an object-oriented language, so you should not even try to write functional code on it.

Of course, this is a superficial interpretation. If you have a little deeper knowledge of C # and imagine its evolution, then you probably know that C # is multi-paradigmal (just like F #) and that even though it was originally primarily imperative and object-oriented, in each subsequent Versions have been added and numerous features continue to be added.

So, the question begs: how good is the current C # language for functional programming? Before answering this question, I will explain what I mean by “functional programming”. This is a paradigm in which:

  1. Focus on working with functions
  2. It is customary to avoid state changes

For a language to facilitate programming in this style, it must:

  1. Support functions as elements of the 1st class; that is, it should be possible to interpret the function as any other value, for example, use functions as arguments or return values ​​of other functions, or store functions in collections
  2. Prevent any partial “local” replacements (or even make them impossible): by default, variables, objects and data structures should be immutable, and it should be easy to create modified versions of an object
  3. Automatically manage memory: after all, we create such modified copies, but do not update the data in place, and as a result we have multiplied objects. This is impractical in a language without automatic memory management.

With all this in mind, we pose the question with an edge:

How functional is C #?

Well ... let's see.

1) Functions in C # are really first class values. Consider, for example, the
following code:

Func triple = x => x * 3;
var range = Enumerable.Range(1, 3);
var triples = range.Select(triple);
triples // => [3, 6, 9]

Here you can see that the functions are really the values ​​of the first class, and you can assign the function to the triple variable, and then set it as the Select argument.

In fact, support for functions as first-class values ​​existed in C # from the first versions of the language, this was done using the Delegate type. Subsequently, lambda expressions were introduced, and syntax-level support for this feature has only improved.

The language reveals some quirks and limitations when it comes to type inference (especially if we want to pass multi-argument functions to other functions as arguments); we will talk about this in Chapter 8. But, in general, support for functions as values ​​of the first class is quite good here.

2) Ideally, the language should also suppress local substitutions. Here is C #'s biggest flaw; everything is mutable by default, and the programmer needs to work hard to ensure immutability. (Compare with F #, where the default variables are immutable, and in order for the variable to be changed, it needs to be specially marked as mutable.)

What about types? There are several immutable types in the framework, for example, string and DateTime, but user-defined mutable types in the language are poorly supported (although, as will be shown below, the situation has been slightly improved in C #, and should also improve in future versions). Finally, the collections in the framework are mutable, but there is already a solid library of immutable collections.

3) On the other hand, in C # a more important requirement is fulfilled: automatic memory management. Thus, although the language does not stimulate a programming style that does not allow local replacements, programming in this style in C # is convenient due to garbage collection.

So, in C # some (but not all) functional programming techniques are very well supported. The language is evolving, and the support for functional techniques in it is improving.

Next, we will consider several features of the C # language from the past, present and foreseeable future - we will talk about features that are especially important in the context of functional programming.

LINQ functional entity

When the C # 3 language was released simultaneously with the .NET 3.5 framework, there turned out to be a lot of possibilities, essentially borrowed from functional languages. Some of them were included in the library ( System.Linq), and some other features provided or optimized certain LINQ features - for example, extension methods and expression trees.

LINQ offers implementations of many common operations on lists (or, more generally, on "sequences", which is exactly what you should call from a technical point of view IEnumerable); the most common of these operations are display, sorting, and filtering. Here is an example that shows all three:

Enumerable.Range(1, 100).
   Where(i => i % 20 == 0).
   OrderBy(i => -i).
   Select(i => $”{i}%”)
// => [“100%”, “80%”, “60%”, “40%”, “20%”]

Notice how Where, OrderBy and Select take other functions as arguments and do not change the resulting IEnumerable, but return a new IEnumerable, illustrating both the principles of the FP I mentioned above.

LINQ allows you to query not only objects in memory (LINQ to Objects), but also various other data sources, for example, SQL tables and XML data. C # programmers have recognized LINQ as a standard tool for working with lists and relational data (and such information accounts for a substantial part of any code base). On the one hand, this means that you already have a little idea of ​​what a functional library API is.

On the other hand, when working with other types, C # specialists usually adhere to the imperative style, expressing the intended behavior of the program in the form of sequential flow control instructions. Therefore, most of the C # code bases that I have ever seen are the stripes of the functional (working with IEnumerableand IQueryable) and the imperative style (everything else).

Thus, although C # -programmers are aware of the advantages of working with a functional library, for example, with LINQ, they are not sufficiently familiar with the principles of the LINQ device, which prevents them from using such techniques in their design.
This is one of the problems that my book is called to solve.

Functionality in C # 6 and C # 7

Although C # 6 and C # 7 are not as revolutionary as C # 3, these versions bring a lot of small changes to the language, which together significantly increase the usability and idiomatic syntax when writing code.

NOTE : Most of the innovations in C # 6 and C # 7 optimize the syntax rather than complement the functionality. Therefore, if you are working with the old version of C #, you can still use all the tricks described in this book (except that there will be a bit more manual work). However, new features significantly increase the readability of the code, and it becomes more pleasant to program in a functional style.

Consider how these features are implemented in the listing below, and then discuss why they are important in the FP.

Listing 1. C # 6 and C # 7 features important in the context of functional programming

using static System.Math;                          <1>
public class Circle
{
   public Circle(double radius) 
      => Radius = radius;                          <2>
   public double Radius { get; }                   <2>
   public double Circumference                     <3>
      => PI * 2 * Radius;                          <3>
   public double Area
   {
      get
      {
         double Square(double d) => Pow(d, 2);     <4>
         return PI * Square(Radius);
      }
   }
   public (double Circumference, double Area) Stats   <5>
      => (Circumference, Area);
}

  1. using staticprovides unskilled access to static members System.Math, for example, PIand Powbelow
  2. Auto-property getter-onlycan only be set in the constructor
  3. Property in expression body
  4. A local function is a method declared inside another method
  5. C # 7 tuple syntax allows member names

Import static members using the «using static»

Guide using staticto C # 6 lets you "import" static members of a class (in this case we are talking about the class System.Math). Thus, the members can be called in this case PIand Powof Mathno extra training.

using static System.Math;
//...
public double Circumference
   => PI * 2 * Radius;

Why is it important? In FP, priority is given to such functions whose behavior depends only on their input arguments, since each such function can be tested separately and discussed about it out of context (compare with instance methods, the implementation of each of them depends on instance members). These functions in C # are implemented as static methods, so the functional library in C # will consist mainly of static methods.

The instruction using staticmakes it easy to consume such libraries, and although abuse of it can lead to pollution of the namespace, moderate use gives clean, readable code.

Simpler immutable types with getter-only auto-properties

When declaring an getter-onlyauto-property, for example,Radius, the compiler implicitly declares a readonly fallback field. As a result, the value of these properties can only be assigned in the constructor or inline.

public Circle(double radius) 
   => Radius = radius;
public double Radius { get; }

Getter-onlyauto-properties in C # 6 make it easy to define immutable types. This can be seen in the Circle class example: it has only one field (reserve field Radius), which is read-only; so, having created Circle, we can no longer change it.

More concise functions with members in the body of an expression A

property is Circumferencedeclared along with the "body of the expression" that begins with =>, and not with the usual "body of instruction" enclosed in { }. Notice how concise this code is compared to the Area property!

public double Circumference
   => PI * 2 * Radius;

In FP, it is customary to write many simple functions, among which is full of single-line. These functions are then compiled into more complex workflows. The methods declared in the body of the expression, in this case, minimize syntactic noise. This is especially evident if you try to write a function that returns a function — this is done very often in the book.

The syntax with the declaration in the body of the expression appeared in C # 6 for methods and properties, and in C # 7 it became more universal and is also used with constructors, destructors, getters and setters.

Local functions

If you have to write many simple functions, this means that often a function is called from just one place. In C # 7, this can be programmed explicitly by declaring methods in the scope of the method; for example, the Square method is declared in the scope of the getter Area.

get
{
   double Square(double d) => Pow(d, 2);
   return PI * Square(Radius);
}

Optimized Tuple Syntax

Perhaps this is the most important feature of C # 7. Therefore, you can easily create and consume tuples and, most importantly, assign meaningful names to their elements. For example, a property Statsreturns a type tuple (double, double), but additionally sets meaningful names for the elements of the tuple, and you can use these elements to refer to them.

public (double Circumference, double Area) Stats
   => (Circumference, Area);

The reason for the importance of tuples in the FP is again explained by the same tendency: to break down tasks into as compact functions as possible. You may not get the data type used to capture information returned by just one function and accepted by another function as input. It would be irrational to determine for such structures the distinguished types that do not correspond to any abstractions from the subject area - just in such cases tuples will come in handy.

Will C # become more functional in the future?

When I wrote a draft of this chapter at the beginning of 2016, I noted with interest that all the features that aroused “strong interest” in the development team are traditionally associated with functional languages. Among these features are:

  • Registered types (immutable types without stencil code)
  • Algebraic data types (a powerful addition to the type system)
  • Pattern matching (resembles the `switch` operator switching the“ form ”of the data, for example, its type, and not just the value)
  • Optimized tuple syntax

However, I had to be content with only the last point. Pattern matching has been implemented to a limited extent, but so far this is only a pale shadow of pattern matching available in functional languages, and in practice this version is usually not enough.

On the other hand, such features are planned in future versions, and the relevant proposals are already being worked out. Thus, in the future we will probably see registered types and pattern matching in C #.

So, C # will continue to evolve as a multi-paradigm language with an increasingly pronounced functional component.

Only registered users can participate in the survey. Please come in.

Relevance of the topic

  • 60.3% Yes, you need just such a specialized book on FI in C # 125
  • 36.7% Need a more comprehensive book on C # 7 76
  • 41.5% Still need a book by Stephen Cleary Concurrency in C #, and no one can translate 86
  • 31.8% Need a book on F # 66

Also popular now: