Functional Programming in C# Part 1

1.0 My notes will cover these points:

- Benefits and tenets of functional programming.
- Functional features of the C# language.
- Representation of function in C#.
- High-Order functions

Functional Programming is a programming paradigm: a different way of thinking about programs than the mainstreams, imperative paradigm

1.1. What's this thing called functional programming?

it's a programming style that emphasizes functions while avoiding state mutation. This definition hits Two fundamental concepts:

functions as first class values.
Avoiding state mutation.
1.1.1 Functions as first class values

we can use them as inputs or outputs of other functions, we can assign them to variables, and we can store them in collections.

we can do with functions all the operations that you can do with values of any other type

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

we declare a functions that returns the triples of a given integer and assigning it to the variables triple. using Range to create an IEnumerable with the values [1,2,3]. Invoking select, giving it the range and triple function as args.

This short snippet demonstrate that functions are indeed first class values in c# as we can assign the multiply-by-3 functions to the variable triple and give it as an arg to select

1.1.2 Avoiding state mutation

Following functional paradigm, we should refrain from state mutation altogether: once create, an obj never changes, and variables should never be reassign

Mutation indicates that a value is changes in-place-updating a value stored somewhere in memory.

int[] nums = { 1, 2, 3 };
nums[0] = 7;
nums // => [7, 2, 3]

the above code creates and populates an array, and then it updates one of the array's values in place. This update also called destructive update as the values stored prior to the update is destroyed. These should always be avoided when coding functionally. (purely functional language don't allow in-place updates at all).

The below code, it's not destructive update and maintain object state as where & orderby don't affect original list. image.png

the following code is not FP approach as List.Sort sorts the list in place

var original = new List<int> { 5, 7, 1 };
original.Sort();
original // => [1, 5, 7]

after sorting, the original ordering is destroyed. u will see why this's problematic right away.

1.1.3.Writing programs with strong guarantees

strong guarantees as functions as first class values initially seems more exciting, and avoiding state mutation is also hugely beneficial, as it eliminates many complexities caused by mutable state.

Mutating state from concurrent processes yields unpredictable results image.png task1 computes and prints out the sum, task2 first sorts the list and then computes and prints the sum.

each of these tasks will correctly compute the sum if run independently. when u run both tasks in parallel, task1 comes up w/ an incorrect and unpredictable result.

Modifying data in place can give concurrent threads an incorrect view of the data image.png

to overcome about this, we use LINQ's OrderBy method to maintain obj state.

Action task3 = () => WriteLine(nums.OrderBy(x => x).Sum());

Parallel.Invoke(task1, task3);
// prints: 0
//         0

The functional approach: creating a new, modified version of the original structure image.png

Functional vs. Object-oriented?

In theory, the fundamental principles of OOP (encapsulation, data abstraction, and so on) are orthogonal to the principles of FP, so there’s no reason why the two paradigms can’t be combined.

In practice, however, most Object-oriented (OO) developers heavily rely on the imperative style in their method implementations, mutating state in place and using explicit control flow: they use OO design in the largem and imperative programming in the small. so We can compare between imperative vs. functional programming

FP differs from OOP in terms of structuring a large application. The difficult art of structuring a complex application relies on several principles:

Modularity (dividing software into reusable components)
Separation of concerns (each component should only do one thing)
Layering (high-level components can depend on low-level components, but not vice versa)
Loose coupling (changes to a component shouldn’t affect components that depend on it)

These principles are generally valid, regardless of whether the component in question is a function, a class or an app.

The functional emphasis on pure functions and composability make it significantly easier ti achieve some of these design goals.

1.2 How functional a language is C#?

C# had support for functions as first class values from the earliest version of the language through the Delegate type and the subsequent introduction of lambda expressions made the syntactic support even better.

Because you create modified versions, rather than updating existing values in place, you want old versions to be garbage collected as needed. Again, C# satisfies this requirement.

the language also discourage in-place updates. we should achieve immutability. Fields and variables must explicitly be marked readonly to prevent mutation

There're a few immutable types in the framework, such as string & DateTime but language support for user-defined immutable types is poor. Collections in the framework are mutable, but a solid library of immutable collection is available

1.2.1 The functional nature of LINQ

LINQ offers implementations for many common operations on lists(or, more generally, on "Sequence" as instance of IEnumerable should technically be called) the most common of which are mapping, sorting and filtering

LINQ facilitates querying not only objects in memory (linq to objects) but various other data source, like SQL table and XML data.

Common Operation on Sequences

The LINQ libarary contains many methods for performing common operations on sequences, such as the following:

Mapping given a sequence and a function, mapping yields a new sequence w/ the elements obtains by applying the given function to each element in the given sequence

Enumerable.Range(1,3).Select(x=> i*3) // [3,6,9]

Filtering given a sequence and a predicate, filtering yields a new sequence consisting of the elements from the given sequence that pass the predicate(in LINQ Where)

Enumerable.Range(1, 10).Where(i => i % 3 == 0) // => [3, 6, 9]

Sorting given a sequence and a key-selector function, sorting yields a new sequence ordered according to the key(in LINQ, OrderBy and OrderByDescending).

Enumerable.Range(1, 5).OrderBy(i => -i) // => [5, 4, 3, 2, 1]
1.2.2 Functional Features in C#6 and C#7

image.png