-
-
Notifications
You must be signed in to change notification settings - Fork 423
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
x
to 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 Add1
and 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 Func<domain, range>
.
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 5
.
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: Func<domain, range>
.
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:
Func<Unit, int>
More on unit
, function syntax and anonymous functions later.
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:
"abc".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
PascalCase
in the normal way - The types all have constructor functions rather than public constructors that you instantiate with
new
. They will always bePascalCase
:
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.Prelude
arecamelCase
.
var x = map(opt, v => v * 2);
- Any extension methods, or anything 'fluent' are
PascalCase
in 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 Prelude.