You’ve seen in previous chapters how FP replaces for
and foreach
loops with LINQ functions like Select()
or Aggregate()
. That’s absolutely terrific, provided you are working with a fixed-length array, or an enumerable that will determine for itself when it’s time to finish iterating.
But what do you do when you aren’t at all sure how long you’ll want to iterate for? What if you’re iterating indefinitely until a condition is met?
I’ll start in this chapter by giving you a nonfunctional implementation of the ancient Indian board game Snakes and Ladders.1 Here are the rules for anyone who had a childhood tragically bereft of this board-game classic:
The board consists of 100 squares. The players start on square 1 and aim to reach square 100.
Each player takes a turn to roll a single die and to move their token forward the number of spaces indicated.
Landing on the bottom of a ladder means the playing piece should “climb” it to the top, advancing closer to square 100.
Landing on the head of a snake means the playing piece should “slide” down it to the end of the tail, moving farther away from square 100.
A player who rolls a 6 may take another turn.
The first player to reach square 100 wins.
This game has all sort of variations, but I’m going with these relatively simple, basic rules. Every edition of the game positions the snakes and ladders in all sorts of ways. The game we’ll be making, based on a version published in the early 20th century, looks like Figure 9-1.
Now, there’s no special reason we have to contain the logic for Snakes and Ladders separately— both do the same job. If a player lands on square x, move them instantly to square y. The only difference between the two is whether the player moves their piece up or down. Logically then, we can treat them as the same thing.
To start off the code, here’s a Dictionary
of all Snakes and Ladders, which square they start on, and which square they end on:
private
static
readonly
Dictionary
<
int
,
int
>
SnakesAndLadders
=
new
Dictionary
<
int
,
int
>
{
{
5
,
15
},
{
8
,
34
},
{
17
,
4
},
{
19
,
60
},
{
25
,
67
},
{
27
,
15
},
{
36
,
16
},
{
48
,
70
},
{
53
,
30
},
{
63
,
99
},
{
64
,
22
},
{
71
,
88
},
{
75
,
93
},
{
80
,
44
},
{
86
,
96
},
{
91
,
69
},
{
95
,
74
},
{
98.
78
}
};
This is what the imperative solution looks like:
public
int
PlaySnakesAndLaddersImperative
(
int
noPlayers
,
IDieRoll
die
)
{
var
currentPlayer
=
1
;
var
playerPositions
=
new
Dictionary
<
int
,
int
>();
for
(
var
i
=
1
;
i
<=
noPlayers
;
i
++)
{
playerPositions
.
Add
(
i
,
1
);
}
while
(!
playerPositions
.
Any
(
x
=>
x
.
Value
>=
100
))
{
var
dieRoll
=
die
.
Roll
();
playerPositions
[
currentPlayer
]
+=
dieRoll
;
if
(
SnakesAndLadders
.
ContainsKey
(
playerPositions
[
currentPlayer
]))
playerPositions
[
currentPlayer
]
=
SnakesAndLadders
[
playerPositions
[
currentPlayer
]];
// another turn for this player if they roll a 6
if
(
dieRoll
==
6
)
continue
;
currentPlayer
+=
1
;
if
(
currentPlayer
>
noPlayers
)
currentPlayer
=
1
;
}
return
currentPlayer
;
}
You can’t possibly implement this functionality with a Select()
statement. We can’t say when the criteria will be met, and we’ll continue to iterate around the while
loop until one of them is.
So how can we make this functional? A while
loop is a statement (specifically, a control flow statement), and as such it’s not preferred by FP languages.
We have a few options, and I’ll describe each, but this is one of those areas requiring some sort of trade-off. Each choice has consequences, and I’ll do my best to explain their respective pros and cons.
Buckle up your seat belts, because here we go.
The classic FP method for handling indefinite loops is to use recursion. In brief, for those of you unfamiliar with it, recursion is the use of a function that calls itself. A condition of some sort determines whether there should be another iteration or whether to actually return data.
If the decision is made at the end of the recursive function, this is known as tail recursion.
A functional and recursive implementation of Snakes and Ladders might look like this:
public
record
Player
{
public
int
Position
{
get
;
set
;
}
public
int
Number
{
get
;
set
;
}
}
public
record
GameState
{
public
IEnumerable
<
Player
>
Players
{
get
;
set
;
}
public
int
CurrentPlayer
{
get
;
set
;
}
public
int
NumberOfPlayers
{
get
;
set
;
}
}
private
static
Player
UpdatePlayer
(
Player
player
,
int
roll
)
{
var
afterDieRoll
=
player
with
{
Position
=
player
.
Position
+
roll
};
var
afterSnakeOrLadder
=
afterDieRoll
with
{
Position
=
SnakesAndLadders
.
ContainsKey
(
afterDieRoll
.
Position
)
?
SnakesAndLadders
[
afterDieRoll
.
Position
]
:
afterDieRoll
.
Position
};
return
afterSnakeOrLadder
;
}
private
static
GameState
PlaySnakesAndLaddersRecursive
(
GameState
state
,
IDieRoll
die
)
{
var
roll
=
die
.
Roll
();
var
newState
=
state
with
{
CurrentPlayer
=
roll
==
6
?
state
.
CurrentPlayer
:
state
.
CurrentPlayer
==
state
.
NumberOfPlayers
?
1
:
state
.
CurrentPlayer
+
1
,
Players
=
state
.
Players
.
Select
(
x
=>
x
.
Number
==
state
.
CurrentPlayer
?
UpdatePlayer
(
x
,
roll
)
:
x
).
ToArray
()
};
return
newState
.
Players
.
Any
(
x
=>
x
.
Position
>=
100
)
?
newState
:
PlaySnakesAndLaddersRecursive
(
newState
,
die
);
}
public
int
PlaySnakesAndLaddersRecursive
(
int
noPlayers
,
IDieRoll
die
)
{
var
state
=
new
GameState
{
CurrentPlayer
=
1
,
Players
=
Enumerable
.
Range
(
1
,
noPlayers
)
.
Select
(
x
=>
(
x
,
1
))
.
Select
(
x
=>
new
Player
{
Number
=
x
.
Item1
,
Position
=
x
.
Item2
}),
NumberOfPlayers
=
noPlayers
};
var
finalState
=
PlaySnakesAndLaddersRecursive
(
state
,
die
);
return
finalState
.
Players
.
First
(
x
=>
x
.
Position
>=
100
).
Number
;
}
Job done, right? Well, not really, and I would think very carefully before using a function like this one. The issue is that every nested function call adds a new item onto the stack in the .NET runtime, and if the code has a lot of recursive calls, that can either negatively affect performance or kill the application with a stack overflow exception.
If there are guaranteed to be only a handful of iterations, nothing is fundamentally wrong with the recursive approach. You’d also have to be sure that this is revisited if the code’s usage is ever significantly changed following an enhancement. This rarely used function with a few iterations one day could turn into something heavily used with hundreds of iterations. If that ever happens, the business might wonder why its wonderful application suddenly becomes near unresponsive almost overnight.
So, as I said, think very carefully before using a recursive algorithm in C#. This has the advantage of being relatively simple and not requiring you to write any boilerplate to make it happen.
F# and many other more strongly functional languages have a feature called tail call optimization, which means it’s possible to write recursive functions without them exploding the stack. This isn’t available in C#, however, and there are no plans to make it available in the future. Depending on the situation, the F# optimization will either create IL code with a while(true)
loop, or use an IL command called goto
to physically move the execution environment’s pointer back to the beginning of the loop.
I did investigate the possibility of referencing a generic tail optimized recursion call from F# and exposing it via a compiled DLL to C#, but that has its own performance issues that makes it a waste of effort.
I’ve seen another possibility discussed online, and that’s to add a post-build event that directly manipulates the .NET IL that C# compiles to so that C# makes retrospective use of the F# tail call optimization feature. That’s clever but sounds too much like hard work to me. It would be a potential extra maintenance task too.
In the next section, I’ll present a technique to simulate tail call optimization in C#.
I’m not entirely sure where the term trampolining came from, but it predates .NET. The earliest references I could find were academic papers from the ’90s, looking at implementing some of the features of LISP in C. I’d guess it’s even a little older than that, though.
The basic idea of trampolining is that we have a function that takes a thunk as a parameter, a thunk being a block of code stored in a variable. In C#, these are implemented as Func
or Action
. Having got the thunk, we create an indefinite loop with while(true)
and some way of assessing a condition that determines whether the loop should terminate. This might be done with an additional Func
that returns a bool
or some sort of wrapper object that needs to be updated with each iteration by the thunk.
But at the end of the day, what we’re looking at is basically hiding a while
loop at the back of our codebase. It’s true that while
isn’t purely functional, but this is one of those places where we might need to compromise. Fundamentally, C# is a hybrid language, supporting both OOP and FP paradigms. There are always going to be places where it’s not going to be possible for C# to behave in exactly the way F# does. This is one of them.
We could implement trampolining in numerous ways, but this is the one I’d tend to go for:
public
static
class
FunctionalExtensions
{
public
static
T
IterateUntil
<
T
>(
this
T
@this
,
Func
<
T
,
T
>
updateFunction
,
Func
<
T
,
bool
>
endCondition
)
{
var
currentThis
=
@this
;
while
(!
endCondition
(
currentThis
))
{
currentThis
=
updateFunction
(
currentThis
);
}
return
currentThis
;
}
}
By attaching to type T
, which is a generic, this extension method therefore attaches to everything in the C# codebase. The first parameter is a Func
delegate that updates the type that T
represents to a new form based on whatever rules the outside world defines. The second is another Func
that returns the condition that will cause the loop to terminate.
Since this is a simple while
loop, there aren’t any issues with the size of the stack. The code is not pure FP, though. It’s a compromise. At the very least, it’s a single instance of a while
loop that’s hidden somewhere, deep in the codebase. It may also be that one day Microsoft will release a new feature that enables proper tail call optimization to be implemented somehow, in which case this function can be reimplemented and the code should continue to work as it did, but with one instance fewer of imperative code features:
public
int
PlaySnakesAndLaddersTrampolining
(
int
noPlayers
,
IDieRoll
die
)
{
var
state
=
new
GameState
{
CurrentPlayer
=
1
,
Players
=
Enumerable
.
Range
(
1
,
noPlayers
)
.
Select
(
x
=>
(
x
,
1
))
.
Select
(
x
=>
new
Player
{
Number
=
x
.
Item1
,
Position
=
x
.
Item2
}),
NumberOfPlayers
=
noPlayers
};
var
finalState
=
state
.
IterateUntil
(
x
=>
{
var
roll
=
die
.
Roll
();
var
newState
=
state
with
{
CurrentPlayer
=
roll
==
6
?
state
.
CurrentPlayer
:
state
.
CurrentPlayer
==
state
.
NumberOfPlayers
?
1
:
state
.
CurrentPlayer
+
1
,
Players
=
state
.
Players
.
Select
(
x
=>
x
.
Number
==
state
.
CurrentPlayer
?
UpdatePlayer
(
x
,
roll
)
:
x
).
ToArray
()
};
return
newState
;
},
x
=>
x
.
Players
.
Any
(
y
=>
y
.
Position
>=
100
));
return
finalState
.
Players
.
First
(
x
=>
x
.
Position
>=
100
).
Number
;
}
There is another way you can implement trampolining. Functionally, it behaves the same as a hidden while
loop and it is pretty much the same in terms of performance as well.
I’m not necessarily convinced that this approach has any additional benefit, and I personally feel it looks a little less friendly than a while
loop. But it is probably ever so slightly more in accordance with the functional paradigm in that it dispenses with the while
statement. Use this alternative version of trampolining if you prefer, but it’s a matter of personal preference, in my view.
This version is implemented using a C# command that under just about any other circumstance I’d implore you not to use. It has existed in coding since at least the days of BASIC and is still around in some form today: the goto
command.
In BASIC, you could move to any arbitrary line of code you wanted by calling goto
and specifying the line number. That’s often how loops were implemented in BASIC. In C#, however, you need to create tags, and it’s only to these tags that goto
will move.
Here’s a reimplementation of IterateUntil()
using two tags. One is called LoopBeginning
, which is the equivalent of the {
character at the beginning of a while
loop. The second tag is called LoopEnding
, which represents the }
character at the end of a while
loop:
public
static
T
IterateUntil
<
T
>(
this
T
@this
,
Func
<
T
,
T
>
updateFunction
,
Func
<
T
,
bool
>
endCondition
)
{
var
currentThis
=
@this
;
LoopBeginning
:
currentThis
=
updateFunction
(
currentThis
);
if
(
endCondition
(
currentThis
))
goto
LoopEnding
;
goto
LoopBeginning
;
LoopEnding
:
return
currentThis
;
}
I’ll leave it to you to decide which version you prefer. They’re pretty much equivalent. Whatever you do, don’t go using goto
anywhere else in your code unless you absolutely, positively, completely, and utterly know what you’re doing and why there’s no better option. Like a certain snake-loving, noseless, evil wizard, the goto
command is both great and yet also terrible if used unwisely.
It’s powerful in that you can create effects and improve efficiency (in some cases) in ways that are impossible via any other means. It’s also dangerous in that you no longer have a predictable order of operations—during execution, the pointer can jump to an arbitrary point in the codebase, regardless of whether it makes any sense at all to do so. Used improperly, you could end up with inexplicable, hard-to-debug issues in your codebase.
Use the goto
statement with great caution.
A third option, presented in the next section, requires quite a bit more boilerplate but ultimately looks a little friendlier than the previous versions. Have a look, and see what you think.
The third option is to hack around with the IEnumerable
and IEnumerator
interfaces. The thing about IEnumerable
is that it isn’t actually an array; it’s just a pointer to the current item of data, and an instruction on how to go and fetch the next item. That being the case, we can create our own implementation of the IEnumerable
interface but with our own behavior.
For our Snakes and Ladders example, we want an IEnumerable
that’s going to iterate until a player has reached square 100 and therefore won the game. We start by creating an implementation of IEnumerable
itself, which has only a single function that has to be implemented: GetEnumerator()
. The IEnumerator
class sits behind the scenes and does the work of enumerating. That’s what we’ll move on to next.
This is what the IEnumerator
interface effectively looks like (it inherits a few functions from other interfaces, so a couple of these functions are here to satisfy the inheritance requirements):
public
interface
IEnumerator
<
T
>
{
object
Current
{
get
;
}
object
IEnumerator
.
Current
{
get
;
}
void
Dispose
();
bool
MoveNext
();
void
Reset
();
}
Each of these functions has a specific job to do, as described in Table 9-1.
Function | Behavior | Returns |
---|---|---|
| Get the current data item. | The current item, or |
| Get the current data item. The same as | Same as |
| Break down everything in the |
|
| Move to the next item. |
|
| Move back to the beginning of the dataset. |
|
Most of the time, IEnumerable
is simply enumerating over an array, in which case I’d imagine the implementation probably should be something vaguely like this:
public
class
ArrayEnumerable
<
T
>
:
IEnumerator
<
T
>
{
public
readonly
T
[]
_data
;
public
int
pos
=
-
1
;
public
ArrayEnumerable
(
T
[]
data
)
{
this
.
_data
=
data
;
}
private
T
GetCurrent
()
=>
this
.
pos
>
-
1
?
_data
[
this
.
pos
]
:
default
;
T
IEnumerator
<
T
>.
Current
=>
GetCurrent
();
object
IEnumerator
.
Current
=>
GetCurrent
();
public
void
Dispose
()
{
// Run! Run for your life!
// Run before the GC gets us all!
}
public
bool
MoveNext
()
{
this
.
pos
++;
return
this
.
pos
<
this
.
_data
.
Length
;
}
public
void
Reset
()
{
this
.
pos
=
-
1
;
}
}
I expect the real code by Microsoft is likely far more complicated—you’d hope there would be a lot more error handling and parameter checking. But this simple implementation gives you an idea of the sort of job the IEnumerator
does.
Knowing how the enumeration process works under the surface, you can see how it’s possible to implement any behavior whatsoever that you’d like in an IEnumerable
. I’ll show you a few examples of how powerful this technique can be by creating an IEnumerable
implementation that iterates through only every other item in an array by putting the following code in MoveNext()
:
public
bool
MoveNext
()
{
pos
+=
2
;
return
this
.
pos
<
this
.
_data
.
Length
;
}
// This turns { 1, 2, 3, 4 }
// into { 2, 4 }
How about an IEnumerable
that loops over every item twice, effectively creating a duplicate of each item when enumerating:
public
bool
IsCopy
=
false
;
public
bool
MoveNext
()
{
if
(
this
.
IsCopy
)
{
this
.
pos
=
this
.
pos
+
1
;
}
this
.
IsCopy
=
!
this
.
IsCopy
;
return
this
.
pos
<
this
.
_data
.
Length
}
// This turns { 1, 2, 3 }
// into { 1, 1, 2, 2, 3, 3 }
Or an entire implementation that goes backward, starting with an enumerator outer wrapper:
public
class
BackwardsEnumerator
<
T
>
:
IEnumerable
<
T
>
{
private
readonly
T
[]
data
;
public
BackwardsEnumerator
(
IEnumerable
<
T
>
data
)
{
this
.
data
=
data
.
ToArray
();
}
public
IEnumerator
<
T
>
GetEnumerator
()
{
return
new
BackwardsArrayEnumerable
<
T
>(
this
.
data
);
}
IEnumerator
IEnumerable
.
GetEnumerator
()
=>
GetEnumerator
();
}
And afterward, the actual IEnumerable
that drives the backward motion:
public
class
BackwardsArrayEnumerable
<
T
>
:
IEnumerator
<
T
>
{
public
readonly
T
[]
_data
;
public
int
pos
;
public
BackwardsArrayEnumerable
(
T
[]
data
)
{
this
.
_data
=
data
??
new
T
[
0
];
this
.
pos
=
this
.
_data
.
Length
;
}
T
Current
=>
(
this
.
_data
!=
null
&&
this
.
_data
.
Length
>
0
&&
this
.
pos
>=
0
&&
this
.
pos
<
this
.
_data
.
Length
)
?
_data
[
pos
]
:
default
;
object
IEnumerator
.
Current
=>
this
.
Current
;
T
IEnumerator
<
T
>.
Current
=>
this
.
Current
;
public
void
Dispose
()
{
// Nothing to dispose
}
public
bool
MoveNext
()
{
this
.
pos
=
this
.
pos
-
1
;
return
this
.
pos
>=
0
;
}
public
void
Reset
()
{
this
.
pos
=
this
.
_data
.
Length
;
}
}
The usage of this backward enumerable is pretty much exactly the same as a standard enumerable:
var
data
=
new
[]
{
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
};
var
backwardsEnumerator
=
new
BackwardsEnumerator
<
int
>(
data
);
var
list
=
new
List
<
int
>();
foreach
(
var
d
in
backwardsEnumerator
)
{
list
.
Add
(
d
);
}
// list = { 8, 7, 6, 5, 4, 3, 2, 1 }
Now that you’ve seen how easy it is to create your own IEnumerable
with whatever custom behavior you want, conjuring up an IEnumerable
that iterates indefinitely should be easy enough.
Try saying this section title 10 times fast!
As you saw in the previous section, there’s no special reason that an IEnumerable
has to start at the beginning and loop to the end. We can make it behave in absolutely any way we care to.
Instead of an array, for this example we’ll pass in a single state object of some kind, along with a bundle of code (i.e., a thunk, or Func
delegate) for determining whether the loop should continue.
Working backward, the first thing we’ll make is the IEnumerator
. This is an entirely bespoke enumeration process, so we’re not going to make any effort to make it generic in any way. The logic wouldn’t make sense outside of a game state object:
public
class
SnakesAndLaddersEnumerator
:
IEnumerator
<
GameState
>
{
// I need this in case of a restart
private
GameState
StartState
;
// old game state -> new game state
private
readonly
Func
<
GameState
,
GameState
>
iterator
;
// some tricky logic required to ensure the final
// game state is iterated. Normal logic is that if
// the MoveNext function returns false, then there isn't
// anything pulled from Current, the loop simply terminates
private
bool
stopIterating
=
false
;
public
SnakesAndLaddersEnumerator
(
Func
<
GameState
,
GameState
>
iterator
,
GameState
state
)
{
this
.
StartState
=
state
;
this
.
Current
=
state
;
this
.
iterator
=
iterator
;
}
public
GameState
Current
{
get
;
private
set
;
}
object
IEnumerator
.
Current
=>
Current
;
public
void
Dispose
()
{
// Nothing to dispose
}
public
bool
MoveNext
()
{
var
newState
=
this
.
iterator
(
this
.
Current
);
// Not strictly functional here, but as always with
// this topic, a compromise is needed
this
.
Current
=
newState
;
// Have we completed the final iteration? That's done after
// reaching the end condition
if
(
stopIterating
)
return
false
;
var
endConditionMet
=
this
.
Current
.
Players
.
Any
(
x
=>
x
.
Position
>=
100
);
var
lastIteration
=
!
this
.
stopIterating
&&
endConditionMet
;
this
.
stopIterating
=
endConditionMet
;
return
!
this
.
stopIterating
||
lastIteration
;
}
public
void
Reset
()
{
// restore the initial state
this
.
Current
=
this
.
StartState
;
}
}
That’s the hard bit done! We have an engine under the surface that’ll allow us to iterate through successive states until we’re finished—whatever we decide “finished” means.
The next item required is the IEnumerable
to run the IEnumerator
. That’s pretty straightforward:
public
class
SnakesAndLaddersIterator
:
IEnumerable
<
GameState
>
{
private
readonly
GameState
_startState
;
private
readonly
Func
<
GameState
,
GameState
>
_iterator
;
public
SnakesAndLaddersIterator
(
GameState
startState
,
Func
<
GameState
,
GameState
>
iterator
)
{
this
.
_startState
=
startState
;
this
.
_iterator
=
iterator
;
}
public
IEnumerator
<
GameState
>
GetEnumerator
()
=>
new
SnakesAndLaddersEnumerator
(
this
.
_iterator
,
this
.
_startState
);
IEnumerator
IEnumerable
.
GetEnumerator
()
=>
GetEnumerator
();
}
Everything is now in place to carry out a custom iteration. We just need to define the custom logic and set up the iterator:
var
state
=
new
GameState
{
CurrentPlayer
=
1
,
Players
=
Enumerable
.
Range
(
1
,
noPlayers
)
.
Select
(
x
=>
(
x
,
1
))
.
Select
(
x
=>
new
Player
{
Number
=
x
.
Item1
,
Position
=
x
.
Item2
}),
NumberOfPlayers
=
noPlayers
};
var
update
=
(
GameState
g
)
=>
{
var
roll
=
die
.
Roll
();
var
newState
=
g
with
{
CurrentPlayer
=
roll
==
6
?
g
.
CurrentPlayer
:
g
.
CurrentPlayer
==
g
.
NumberOfPlayers
?
1
:
g
.
CurrentPlayer
+
1
,
Players
=
g
.
Players
.
Select
(
x
=>
x
.
Number
==
g
.
CurrentPlayer
?
UpdatePlayer
(
x
,
roll
)
:
x
).
ToArray
()
};
return
newState
;
};
var
salIterator
=
new
SnakesAndLaddersIterator
(
state
,
update
);
We have a couple of options for handling the iteration itself, and I’d like to take a little time out to discuss each of those options in more detail.
Strictly speaking, as a fully fledged iterator, any LINQ operation can be applied, as well as a standard foreach
iteration.
Using foreach
would probably be the simplest way to handle this iteration, but it wouldn’t be strictly functional. It’s up to you whether you want to compromise by adding a statement in a limited capacity, or want to seek out a more purely functional alternative. Implemented with a foreach
, the code might look like this:
foreach
(
var
g
in
salIterator
)
{
// store the updated state outside of the loop.
playerState
=
g
;
// Here you can do whatever logic you'd like to do
// to message back to the player. Write a message onto screen
// or whatever is useful for them to be prompted to do another action
}
// At the end of the loop here, the player is now out of jail, and
// the game can continue with the updated version of playerState;
That code wouldn’t give me too much cause for concern in production, honestly. But what we’ve done is negated all the work we’ve put into attempting to get rid of nonfunctional code from our codebase.
The other options use LINQ. As a fully fledged IEnumerable
, our salIterator
can have any LINQ operations applied to it. Which ones would be the best, though?
Select()
would be an obvious starting place but might not behave entirely as you’d expect. Its usage is pretty much the same as any standard Select()
list operation you’ve ever done before:
var
gameStates
=
salIterator
.
Select
(
x
=>
x
);
The trick here is that we’re treating gameIterator
as an array, so Select
ing from it will result in an array of game states. We’ll basically have an array of every intermediate step the user has gone through, finishing with the final state in the last element.
The easy way to reduce this to simply the final state is to substitute Select()
for Last()
:
var
endState
=
var
gameStates
=
salIterator
.
Last
();
This assumes, of course, that you aren’t interested in the intermediate steps. It might be that you want to compose a message to the user for each state update, in which case you might want to select and then provide a transformation:
var
messages
=
salIterator
.
Select
(
x
=>
"Player "
+
x
.
CurrentPlayer
+
"'s turn."
+
"The current winner is: "
+
x
.
Players
.
Single
(
y
=>
y
.
Number
==
x
.
Players
.
Max
(
z
=>
z
.
Position
))
);
That eradicates the actual game state, though, so Aggregate()
might be a better option:
var
stateAndMessages
=
(
Messages
:
Enumerable
.
Empty
<
string
>(),
State
:
state
);
var
result
=
salIterator
.
Aggregate
(
stateAndMessages
,
(
agg
,
x
)
=>
{
return
(
agg
.
Messages
.
Append
(
"Player "
+
x
.
CurrentPlayer
+
"'s turn."
+
"The current winner is: "
+
x
.
Players
.
First
(
y
=>
y
.
Position
==
x
.
Players
.
Max
(
z
=>
z
.
Position
)).
Number
),
x
);
});
The x
in each iteration of the Aggregate()
process is an updated version of the game state, and the code will carry on aggregating until the declared end condition is met. Each pass appends a message to the list, so what you finally get at the end is a tuple containing an array of strings, which are messages to pass to the player, and the final version of the game state.
Bear in mind that any use of LINQ statements that will terminate the iteration early in some manner—such as First
or Take
—will also prematurely end this iteration process, possibly in our instance with the player still in jail. Of course, this might be a behavior you want! For example, maybe you’re restricting the player to just a couple of actions before moving on to another part of the game or to another player’s turn. You could come up with all sorts of possibilities for the logic by playing with this technique.
You’ve learned how to implement indefinite iteration in C# using the foreach
statement, which results in cleaner code with fewer possible side effects of execution.
It’s not strictly possible to do this purely functionally. Several options are available, all of which carry some level of compromise to the functional paradigm. This is the nature of working in C#. Which option, if any, you wish to use is entirely a matter of personal choice and whatever of any constraints that apply to your project.
More often than not, I go with the trampolining option. It isn’t as dangerous as recursion, nor as much work to implement as a custom iterator. As always with these things, your choice depends what you’re trying to do. The requirements and constraints of your project will determine the best option.
Please be cautious with the use of recursion. It’s a fast method of iteration that’s purely functional, but if you aren’t careful, it can lead to significant performance issues when it comes to memory usage.
In the next chapter, I’ll show you a nice way to take advantage of pure functions to improve performance in your algorithms.
1 Also known as Chutes and Ladders in the US.
18.119.105.239