📜 ⬆️ ⬇️

C # language is almost functional

Hello, dear readers! Our searches in the field of C # language have much in common with this article, the author of which is a specialist in functional programming in C #. The article is an excerpt from the upcoming book, so at the end of the post we suggest voting for this book.



Many programmers implicitly imply that "functional programming (FP) should be implemented only 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-paradigm (just like F #) and that, even if it was originally imperative and object-oriented, in each subsequent versions added and continue to add numerous features.
')
So, the question arises: 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. Emphasis is placed on working with functions.
  2. It is customary to avoid state changes.

In order for a language to promote programming in this style, it must:

  1. Support functions as elements of 1st class; that is, it should be possible to treat a function like 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 make them impossible at all): variables, objects, and default data structures should be immutable, and it should be easy to create modified versions of the object
  3. Automatically manage memory: after all, we create such modified copies, and do not update the data on the spot, and as a result we multiply objects. This is impractical in a language where there is no automatic memory management.

With all this in mind, we put the question squarely:

How much is C # functional?

Well ... let's see.

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

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

Here you can see that functions are really first-class values, and you can assign a function to a triple variable, and then set it as a 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 support for this feature 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'll talk about this in Chapter 8. But, in general, function support as first-class values ​​is quite good here.

2) Ideally, the language should also suppress local replacements. Here is the biggest drawback of C #; everything is changeable by default, and the programmer needs a lot of work to ensure immutability. (Compare with F #, where variables are immutable by default, and in order for a variable to be changed, it must be specifically marked as mutable.)

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

3) On the other hand, C # fulfills a more important requirement: automatic memory management. Thus, although the language does not stimulate a programming style that does not allow local substitutions, 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 look at several features of C # from the past, present, and foreseeable future — we will focus on features that are especially important in the context of functional programming.

LINQ functional entity

When C # 3 came out at the same time as the .NET 3.5 framework, there were 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 proposes implementations of many common list operations (or, more generally, over “sequences”, that’s the technical point of view to call IEnumerable ); The most common of these operations are mapping, sorting, and filtering. Here is an example in which all three are presented:

 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 FP principles I mentioned above.

LINQ allows you to query not only objects in memory (LINQ to Objects), but also various other data sources, such as SQL tables and data in XML format. Programmers working with C # have recognized LINQ as the 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 what the API of the functional library is.

On the other hand, when working with other types, C # specialists usually follow an imperative style, expressing the intended behavior of the program in the form of sequential flow control instructions. Therefore, most of the C # code bases I’ve seen are intersecting the functional (working with IEnumerable and IQueryable ) and imperative styles (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 themselves when designing.
This is one of the problems that my book is intended to solve.

Functionality in C # 6 and C # 7

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

NOTE : Most innovations in C # 6 and C # 7 optimize syntax, rather than complement the functionality. Therefore, if you work with the old version of C #, you will still be able to use all the techniques described in this book (unless there will be a little more manual work). However, new features greatly increase the readability of the code, and programming in the functional style becomes more pleasant.

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

Listing 1. C # 6 and C # 7 features that are 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 static provides unqualified access to the static System.Math members, for example, PI and Pow below
  2. The getter-only auto property can getter-only be set in the constructor.
  3. Property in the body of the expression
  4. Local function is a method declared inside another method.
  5. C # 7 tuple syntax allows member names

Import static members with using static

The using static in C # 6 allows you to “import” static class members (in this case, the System.Math class). Thus, in our case, you can call the members of PI and Pow from Math without additional qualifications.

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

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

The using static facilitates the consumption of such libraries and, although its abuse can lead to namespace pollution, moderate use gives clean, readable code.

More simple immutable types with getter-only auto-properties

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

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

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

More concise functions with members in the body of the expression

The Circumference property Circumference declared along with the “expression body”, which begins with => , and not with the usual “instruction body” contained in { } . Pay attention to how concise this code is compared to the Area property!

 public double Circumference => PI * 2 * Radius; 

In FP, it is customary to write a set of simple functions, among which there is a full one-line function. These functions are then assembled into more complex worker control threads. Methods declared in the body of the expression, in this case, minimize syntactic interference. This is especially obvious if you try to write a function that returns a function — this is done very often in a 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 designers, destructors, getters and setters.

Local functions

If you have to write a lot of 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 method scope; for example, the Square method is declared in the Area getter scope.

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

Optimized tuple syntax

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

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

The reason for the importance of tuples in the OP is, again, explained by the same tendency: to break tasks into as compact functions as possible. You may not have a data type that is 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 selected types that do not correspond to any abstractions from the subject area — exactly in such cases tuples will be useful.

Will C # become more functional in the future?

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


However, I had to be content with only the last point. Comparison with the template has been implemented to a limited extent, but for now this is only a faint shadow of the comparison with the template available in functional languages, and in practice this version is usually not enough.

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

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

Source: https://habr.com/ru/post/321546/


All Articles