In the previous article, we discussed and implemented domain types for the Mars Rover kata. Now, we will go through the rest of the requirements and implement the command execution functionality.
First, we need to parse an input sequence of characters in order to determine which movement functions should be executed:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let parseInput chars = | |
let commands: Command list = List.Empty | |
Seq.toList chars | |
|> List.fold (fun commands char -> | |
match char with | |
| 'L' -> Command.RotateLeft :: commands | |
| 'R' -> Command.RotateRight :: commands | |
| 'M' -> Command.Move :: commands | |
| _ -> commands | |
) commands | |
|> List.rev |
The output of the parseInput function is a list of commands (Command discriminated union type), so we need to map that output to appropriate functions before the execution of a rover movement:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let toFunc = | |
function | |
| Command.RotateLeft -> rotateLeft | |
| Command.RotateRight -> rotateRight | |
| Command.Move -> calculateNewCoordinates |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Command -> (RoverPosition -> RoverPosition) |
We could easily execute the resulting list of functions just by applying List.fold because every function in the list has the following signature:
The output of the first function matches the input of the second function. The output of the second function matches the input of the third function, etc. These functions are easily composable.
But, things are not so simple. If we look at the requirements again, we can see that a rover can hit an obstacle. In that case, the rover should stop and return the position of the obstacle. A movement can be either successful or not:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RoverPosition -> RoverPosition |
But, things are not so simple. If we look at the requirements again, we can see that a rover can hit an obstacle. In that case, the rover should stop and return the position of the obstacle. A movement can be either successful or not:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let move command obstacles position = | |
let newPosition = position |> command | |
let isObstacle = List.contains newPosition.location obstacles | |
newPosition |> if isObstacle then Error else Ok |
Signature of the move function is:
Now, imagine that we have a sequence of movement functions that we want to compose together. The output of the first movement function does not match the input of the second movement function. The output of the second movement function does not match the input of the third movement function, etc. The first two parameters (command and the list of obstacles) can be baked into the move function but, the issue then is the signature of the partially applied function:
Luckily, the solution to this problem already exists in functional programming: Railway Oriented Programming. I strongly suggest you read the original explanation by Scott Wlaschin.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(RoverPosition -> RoverPosition) -> Location list -> RoverPosition -> Result<RoverPosition,RoverPosition> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
RoverPosition -> Result<RoverPosition,RoverPosition> |
I will introduce you to the basics and then we will try to apply the theory to our problem.
So, why railways? Well, railways have tracks. A track can be a good metaphor for a function. Functions are transformations that turn input into an output (Figure 1). The composition then comes naturally as long as the output of one function matches the input of some other function.
So, why railways? Well, railways have tracks. A track can be a good metaphor for a function. Functions are transformations that turn input into an output (Figure 1). The composition then comes naturally as long as the output of one function matches the input of some other function.
Figure 1.
When we try to execute a movement function, we either hit an obstacle or we proceed to the next position and execute the next function (if there is one). Railways have another great analogy for this scenario: switches (Figure 2):
Figure 2.
Now it's obvious why we can not glue movement functions together. The move function may end up in one of the two possible tracks. The correct way of applying the composition, in this case, looks like this:
Figure 3.
The top track is the happy path and the bottom track is the failure track. In order to achieve this in the code, we need a function that converts a switch function into a proper two-track function.
Here's the implementation of that function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let bind switchFunction twoTrackInput = | |
match twoTrackInput with | |
| Ok s -> switchFunction s | |
| Error f -> Error f |
It takes a switch function (move in our case) as a parameter and returns a new function that takes a two-track input. If the input is Ok it calls the switch function with the appropriate value and if the input is an Error, the switch function is omitted.
With everything in place, we can implement the main command execution function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let execute position obstacles commands = | |
parseInput commands | |
|> List.map toFunc | |
|> List.fold (fun position command -> | |
bind (move command obstacles) position) | |
(Ok position) | |
|> formatOutput |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let execute position obstacles commands = | |
parseInput commands | |
|> List.map toFunc | |
|> List.fold (fun position command -> | |
position >>= move command obstacles) | |
(Ok position) | |
|> formatOutput |
The only thing left is to show the implementation of the formatOutput function:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let formatOutput result = | |
let toStr p = | |
sprintf "%O:%O:%O" p.location.x p.location.y p.direction | |
match result with | |
| Ok p -> toStr p | |
| Error p -> toStr p |> sprintf "O:%s" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
seq<char> -> string //Required execute signature | |
RoverPosition -> Location list -> seq<char> -> string //Actual execute signature |