Variables are just syntactic sugar for lambda expressions: they are not stricly necessary and ideally C# could happily live without them. Even the reseved word var
could be eliminated.
In this post we will use ReSharper for refactoring variables away, transforming them to anonymous lambda expressions.
That’s a trick that will come in handy for deriving the Y Combinator. I wrote about this topic back in 2018, in the excitement of reading Structure and Interpretation of Computer Programs.
SICP claims that:
“[A variable] is simply syntatic sugar for the underlying lambda expression”
where “syntatic sugar” is defined as:
“convenient alternative surface structures for things that can be written in more uniform ways”
In other words, the claim is that variables make code sweeter to write and read, but they add no expressive power.
Let’s go straight to the code.
Here’s a snippet using 3 variables:
[Fact]
void variables_are_used()
{
var amount = 7;
var product = "beers";
var price = 3;
Assert.Equal(
"7 beers cost 21 EUR",
$"{amount} {product} cost {amount * price} EUR");
}
Squeezing your eyes, you could see how this is equivalent to the (more convoluted):
[Fact]
void variables_are_replaced_with_lambdas()
{
Assert.Equal(
"7 beers cost 21 EUR",
new Func<int, string, int, string>(
(price, product, amount) =>
$"{amount} {product} cost {amount * price} EUR")
(3, "beers", 7)
);
}
where there are no variables at all.
By the end of this post you will learn how to get from the former to the latter implementation, only applying refactoring moves.
Let me be clear: the latter form is way less readable than the original one, and by no means better.
Yet, the transformation to derive it is a not obvious refactoring move that can come in handy, especially when playing with functional programming.
Also, it’s fun to derive. So, why not?
Let’s start from a very prosaic equality: any variable can be replaced by a parameterless constant function.
In:
[Fact]
void variable_is_used()
{
var foo = "bar";
Assert.Equal(
"bar",
foo);
}
var foo = "bar"
can be replaced with string foo() { return "bar"; }
, getting to:
[Fact]
void variable_is_replaced_with_method()
{
string foo() =>
"bar";
Assert.Equal(
"bar",
foo());
}
That’s insultingly trivial, isn’t it?
Bear with me. Let’s see what happens if we use R# to make that foo()
function an anonymous lambda.
By the way: getting from var foo = "bar";
to string foo() => "bar"
is by itself a refactoring; it’s indeed a matter of applying Extract Method on "bar"
and of inlining foo
with Inline Variable.
It’s much more interesting to see what happens when we convert foo()
to an anonymous lambda.
We can proceed as follows:
Select "bar"
and apply Extract Method
[Fact]
void variable_is_replaced_with_method()
{
var foo = Temp();
Assert.Equal(
"bar",
foo);
}
private string Temp() =>
"bar";
Ideally, we could have done the same with Extract Local Function: for some reasons, though, R# would not be able to perform the next step.
Func
Extract Temp
(not Temp()
, only Temp
, without the ()
) in the expression var foo = Temp()
as a variable with Introduce Variable. This is the crucial step: it will convert a method to an equivalent variable of type Func<T>
:
[Fact]
void variable_is_replaced_with_method()
{
Func<string> temp = Temp;
var foo = temp();
Assert.Equal(
"bar",
foo);
}
private string Temp() =>
"bar";
Apply Inline Variable, in order, to Temp
, to temp
and finally to foo
[Fact]
void variable_is_replaced_with_anonymous_lambda()
{
Assert.Equal(
"bar",
((Func<string>)(() =>
"bar"))
());
}
Here we are. Both the variable and the named function have disappeared, leaving us with just an anonymous lambda.
Notice the trailing ()
: this invokes the parameterless anonymous lambda, feeding it no value, and getting back "bar"
.
To understand why we got ()
, it is useful to review what we got when we extracted Temp()
:
[Fact]
void variable_is_replaced_with_method()
{
var foo = Temp();
Assert.Equal(
"bar",
foo);
}
private string Temp() =>
"bar";
Temp()
is parameterless, which generates a parameterless lambda. If it had a parameter, also the final anonymous lambda would have had one.
Let’s try.
We can promote "bar"
to a parameter, applying Introduce Parameter:
[Fact]
void variable_is_replaced_with_method()
{
var foo = Temp("bar");
Assert.Equal(
"bar",
foo);
}
private string Temp(string value) =>
value;
Func
Like before, extract Temp
(without the ("bar")
part) in the expression var foo = Temp("bar")
[Fact]
void variable_is_replaced_with_method()
{
Func<string, string> converter = Temp;
var foo = converter("bar");
Assert.Equal(
"bar",
foo);
}
private string Temp(string value) =>
value;
Interesting! We get a Func<string, string>
instead of a simple Func<string>
.
Just like before, inline Temp
, temp
and foo
. We get:
[Fact]
void variable_is_replaced_with_method()
{
Assert.Equal(
"bar",
((Func<string, string>)(value =>
value))
("bar"));
}
As is turned out, we get ("bar")
instead of ()
.
That’s basically it. Any:
VariableType variable = some_value;
{
[body_using_variable]
}
can be replaced with
Func<VariableType, BodyType>(variable =>
[body_using_variable]
)
(some_value)
where the original variable name is replaced with an anonymous lambda parameter, and its value with the argument the lambda is fed with.
Notice that the body of the Func
in
[Fact]
Assert.Equal(
"bar",
((Func<string, string>)(value =>
value))
("bar"));
is the identity function value => value
: this is because we converted the constant function.
Let’s go one level up, starting from a more complex function and using more variables.
Replacing multiple variables with a lambda is equally mechanical. Let’s use the initial sample snippet:
[Fact]
void variables_are_used()
{
var amount = 7;
var product = "beers";
var price = 3;
Assert.Equal(
"7 beers cost 21 EUR",
$"{amount} {product} cost {amount * price} EUR");
}
Apply Extract Method to the body, that is to $"{amount} {product} cost {amount * price} EUR")
[Fact]
void variables_are_used()
{
var amount = 7;
var product = "beers";
var price = 3;
Assert.Equal(
"7 beers cost 21 EUR",
Temp(amount, product, price));
}
private static string Temp(int amount, string product, int price) =>
$"{amount} {product} cost {amount * price} EUR";
Func
Convert Temp
(without its arguments) to an anonymous lambda, with Introduce Variable:
[Fact]
void variables_are_used()
{
var amount = 7;
var product = "beers";
var price = 3;
Func<int, string, int, string> temp = Temp;
Assert.Equal(
"7 beers cost 21 EUR",
temp(amount, product, price));
}
private static string Temp(int amount, string product, int price) =>
$"{amount} {product} cost {amount * price} EUR";
Notice how type we get, Func<int, string, int, string>
, it’s basically Func<type_var_1, type_var_2, type_var_3, type_body>
`
Inline Temp
with Inline Method
[Fact]
void variables_are_used()
{
var amount = 7;
var product = "beers";
var price = 3;
Func<int,string,int,string> temp = (amount, product, price) => $"{amount} {product} cost {amount * price} EUR";
Assert.Equal(
"7 beers cost 21 EUR",
temp(amount, product, price));
}
Apply Inline Variable to to get rid of all the variables:
[Fact]
void variables_are_used()
{
Func<int,string,int,string> temp =
(amount, product, price) =>
$"{amount} {product} cost {amount * price} EUR";
Assert.Equal(
"7 beers cost 21 EUR",
temp(7, "beers", 3));
}
Finally, inline temp
:
[Fact]
void variables_are_used()
{
Assert.Equal(
"7 beers cost 21 EUR",
((Func<int, string, int, string>)(
(amount, product, price) =>
$"{amount} {product} cost {amount * price} EUR"))
(7, "beers", 3));
}
Is that an end in itself?
Just a bit, but not quite. This is indeed one of the moves for replacing procedural code with functional expressions.
The equivalence between let
expressions and lambdas is very well known in the Lisp’s circles. It’s not in C#’s, but sooner or later the experienced Functional Programmer will bump into it.
So what’s next? Go and use it for deriving Y. Have fun!