In this exercise we are going to solve a variation of the robot kata. This version is called the Lost Robot kata.

As in the original kata, we have a robot that can move forward and turn left and right. But this time, the area that the robot can travel goes from -3 to 3 in the X direction and -3 to 3 in the Y direction. As long as the robot is within this square things are fine. If the robot leaves this square, then it is considered lost and it does not respond to any more commands.

As before, given a series of commands, we need to display the final position of the robot, or if it is lost then display the message "Robot Lost"

## Solving the kata

The first thing we need to decide is how we want to store the information that the robot is lost. We could make the robot state to `None` (the most common way) or have a flag which says that the robot is lost. Both these solutions are ugly because we will need to add null checks to every function before doing the calculation.

Instead we will take inspiration from the previous article and wrap the robot state with a `Maybe` monad. If the robot has a valid state then it will be `Just(state)` and if the robot is lost then it will be `Nothing`.

We are reuse the implementation of `move`, `left` and `right` from the previous kata. Here is the code for reference

``````def move(robot: RobotState) -> RobotState:
(x, y), dir = robot
match dir:
case "North":
return make_robot((x, y + 1), dir)
case "South":
return make_robot((x, y - 1), dir)
case "East":
return make_robot((x + 1, y), dir)
case "West":
return make_robot((x - 1, y), dir)

def left(robot: RobotState) -> RobotState:
pos, dir = robot
match dir:
case "North":
return make_robot(pos, "West")
case "South":
return make_robot(pos, "East")
case "East":
return make_robot(pos, "North")
case "West":
return make_robot(pos, "South")

def right(robot: RobotState) -> RobotState:
pos, dir = robot
match dir:
case "North":
return make_robot(pos, "East")
case "South":
return make_robot(pos, "West")
case "East":
return make_robot(pos, "South")
case "West":
return make_robot(pos, "North")``````

Now we write a function that will check if the robot state is within bounds. If it is, then we wrap the result in a `Just` otherwise return `Nothing` (see the previous article on monads for the implementation of these two classes)

``````def check_position(robot: RobotState):
match robot:
case ((x, y), _) if -3 <= x <= 3 and -3 <= y <= 3:
return Just(robot)
case _:
return Nothing()``````

This check should be performed after doing a move, so we will compose this to the end of the move

``move = compose(move, check_position)``

Executing the move function will now run the original move calculation followed by the check. This composed move function takes a `RobotState` type as input and returns a `Maybe` monad as output.

`left` and `right` functions cannot cause the robot to get lost, so they remain as they were before.

Thats pretty much it. Yes, really, the kata is solved with this one function. We don't need to touch any of the existing code from the previous kata.

To compose a sequence of operations, we use `map` and `flatmap`.

``````start = make_robot((0, 0), "North")
end = (move(start)
.map(left)
.flatmap(move)
.flatmap(move)
.map(left)
.flatmap(move)
.flatmap(move)
.flatmap(move)
.map(right)
.map(right)
.map(right)
.flatmap(move))

match end:
case Just():
print(f"Robot is at {end.val}") # Robot is at ((-1, -2), 'East')
case Nothing():
print("Robot lost")``````

`left` and `right` are ordinary functions so we use `map` to compose them. `move` returns a `Maybe` so it requires `flatmap` for composition.

And if the robot were to get lost

``````start = make_robot((0, 0), "North")
end = (move(start)
.map(left)
.flatmap(move)
.flatmap(move)
.flatmap(move)
.flatmap(move) # robot gets lost here
.map(left)
.flatmap(move)
.flatmap(move)
.flatmap(move)
.map(right)
.map(right)
.map(right)
.flatmap(move))

match end:
case Just():
print(f"Robot is at {end.val}")
case Nothing():
print("Robot lost") # Robot Lost``````

Notice how the robot gets lost somewhere in the middle, but we don't need to add any check. We just compose the entire sequence that we want to execute and at the end we will get to know if the whole sequence failed or succeeded (and if success, what is the output).

The job of keeping track of the execution state is handled by the monad, leaving our code extremely clean and readable.