Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Thinking Functionally: Function values
Let’s look at the simple function again:
int Add1(int x) => x + 1;
What does the
x mean here? It means:
- Accept some value from the input domain.
- Use the name
xto represent that value so that we can refer to it later.
This process of using a name to represent a value is called "binding". The name
x is "bound" to the input value.
So if we evaluate the function with the input
5 say, what is happening is that everywhere we see
x in the original definition, we replace it with
5, sort of like search and replace in a word processor.
Add1(5) // replace "x" with "5" // Add1(5) == 5 + 1 == 6 // result is 6
It is important to understand that this is not assignment.
x is not a “slot” or variable that is assigned to the value and can be assigned to another value later on. It is a onetime association of the name
x with the value. The value is one of the predefined integers, and cannot change. And so, once bound, x cannot change either; once associated with a value, always associated with a value.
This concept is a critical part of thinking functionally: there are no “variables”, only values.
If you think about this a bit more, you will see that the name
Add1 itself is just a binding to “the function that adds one to its input”. The function itself is independent of the name it is bound to.
When you type
int Add1(int x) => x + 1; you are telling the C# compiler “every time you see the name
Add1, replace it with the function that adds 1 to its input”.
Add1 is called a function value.
To see that the function is independent of its name, try:
Func<int, int> plus1 = Add1; int r1 = Add1(5); int r2 = plus1(5);
You can see that
plus1 are two names that refer ("bound to") to the same function.
You can always identify a function value because its signature has the standard form
Imagine an operation that always returned the integer
5 and didn’t have any input.
This would be a “constant” operation.
How would we write this in C#? We want to tell the C# compiler “every time you see the name
C, replace it with
5”. Here’s how:
const int C = 5;
There is no mapping this time, just a single int. What’s new is an equals sign with the actual value printed after it. The C# compiler knows that this binding has a known value which it will always return, namely the value
In other words, we’ve just defined a constant, or in C# terms, a simple value. To bring this into parity with the function definition above it could be represented thus:
static int C => 5;
That has the same behaviour as the
const int when used, but has the signature
Func<int>. There is no mapping from domain to range as before:
Simple values vs. function values
It is important to understand that in functional programming, unlike idiomatic C#, there is very little difference between simple values and function values. They are both values (of
Func) which can be bound to names and then passed around. And in fact, one of the key aspects of thinking functionally is exactly that: functions are values that can be passed around as inputs to other functions, as we will soon see.
Note that there is a subtle difference between a simple value and a function value. A function always has a domain and range and must be “applied” to an argument to get a result. A simple value does not need to be evaluated after being bound. Using the example above, if we wanted to define a “constant function” that returns five we would have to use:
Func<Unit, int> C = _ => 5; // or static int C(Unit _) => 5;
The signature for these functions is:
unit, function syntax and anonymous functions later.
"Values" vs. "Objects"
In a functional programming language like F#, most things are called "values". In an object-oriented language like C#, most things are called "objects". So what is the difference between a "value" and an "object"?
A value, as we have seen above, is just a member of a domain. The domain of ints, the domain of strings, the domain of functions that map ints to strings, and so on. In principle, values are immutable. And values do not have any behaviour attached them.
An object, in a standard definition, is an encapsulation of a data structure with its associated behaviour (methods). In general, objects are expected to have state (that is, be mutable), and all operations that change the internal state must be provided by the object itself (via "dot" notation).
In F#, even the primitive values have some object-like behavior. For example, you can dot into a string to get its length:
But, in general, we want to avoid using "object" for standard values when discussing the functional approach in C#, reserving it to refer to instances of true classes, or other values that expose member methods.
One of the areas that's likely to get seasoned C# heads worked up is my choice of naming style. The intent is to try and make something that 'feels' like a functional language rather than follows the 'rule book' on naming conventions (mostly set out by the BCL).
There is however a naming guide that will stand you in good stead whilst reading through this documentation:
- Type names are
PascalCasein the normal way
- The types all have constructor functions rather than public constructors that you instantiate with
new. They will always be
Option<int> x = Some(123); Option<int> y = None; List<int> items = List(1,2,3,4,5); Map<int, string> dict = Map((1, "Hello"), (2, "World"));
- Any (non-type constructor) static function that can be used on its own by
using static LanguageExt.Preludeare
var x = map(opt, v => v * 2);
- Any extension methods, or anything 'fluent' are
PascalCasein the normal way
var x = opt.Map(v => v * 2);
Even if you don't agree with this non-idiomatic approach, all of the
camelCase static functions have fluent variants, so actually
you never have to see the 'non-standard' stuff.
If you're not using C# 6 yet, then you can still use this library. Anywhere in the docs below where you see a camelCase function
it can be accessed by prefixing with