At the bottom of the article "How do you become better at programming?" is a mention of the robot kata. In this article, we will look at how to solve it using a functional programming approach.
The Robot Kata
Here is the problem statement:
- Imagine a robot that is at position (0, 0) and facing North.
- It can take 3 commands: Move forward, turn left or turn right.
- Turning left or right changes the orientation of the robot by 90 degrees without changing the position. For example, turn left will make the robot remain at (0, 0) but it will face West now
- Moving forward will move the robot 1 step in the direction faced, so if it is at (0, 0) and facing West, after moving forwards it will be at (-1, 0) and still facing West
Given a sequence of commands (eg: FLFFLFFFRRRF) then calculate the final position and orientation of the robot.
In this article, we will see how to solve this kata using a functional programming style.
Storing the data
First, we need to decide how we want to represent the data. The robot has 3 state variables:
- The current x coordinate
- The current y coordinate
- The direction it is facing: E, S, W, N
We have multiple options to represent this data. We could create a class
, use a dataclass
, use a namedtuple
, or just a regular tuple
.
Since this is a simple application, we will just go with a regular tuple
To make the data clearer, let us use Python's type hinting feature to create some data types.
Direction
can be one ofNorth
,South
,East
orWest
Position
is a tuple containing thex
andy
coordinates- The overall
RobotState
is a tuple containing thePosition
andDirection
from typing import Tuple, Literal
Direction = Literal["North", "South", "East", "West"]
Position = Tuple[int, int]
RobotState = Tuple[Position, Direction]
(If you are using Python 3.10+ then you can replace Tuple
with tuple
.)
Now, we write a function to create the tuple. Given the position
and direction
it will just make a tuple out of it
def make_robot(position: Position, direction: Direction) -> RobotState:
return (position, direction)
Moving the robot
Now that we have a way to represent the robot state, we can implement the three functions that we need. We will use structural pattern matching feature here.
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")
Each of these functions take in one RobotState
as input and return a new RobotState
as the output.
One interesting thing to note here is how we use the iterable unpacking feature with the tuple
(x, y), dir = robot
If you have never used type hints, iterable unpacking or structural pattern matching, then you should definitely take a look at them. They can really make your python code much easier to read and maintain. We will be doing articles on all those topics shortly.
Composing the functions
Once we have the basic functions written, we need to be able to compose them together so that we can give a sequence of commands.
Here is a compose function like the one we wrote previously, but can take any number of functions for composition
def compose(*funcs):
first, second, *rest = funcs
if rest:
second = compose(second, *rest)
return lambda *args, **kwargs: second(first(*args, **kwargs))
With that we can now write a function to execute a sequence of commands
def run_commands(commands: str):
command_mapping = {"L": left, "R": right, "F": move}
functions = [command_mapping[command] for command in commands]
return compose(*functions)
and we can run a sequence
sequence = run_commands("FLFFLFFFRRRF")
start = make_robot((0, 0), "North")
end = sequence(start)
print(end) # Output is ((-1, -2), "East")
Summary
In this article, we saw how to implement the robot kata using a functional programming style in python. We create the basic functions move
, left
and right
and then we can use compose
to combine them in any order and calculate any sequence of movement.
In order for compose to work, the output of one function must match the input of the next function. What happens if this is not the case? That is the topic of the next article.
Did you like this article?
If you liked this article, consider subscribing to this site. Subscribing is free.
Why subscribe? Here are three reasons:
- You will get every new article as an email in your inbox, so you never miss an article
- You will be able to comment on all the posts, ask questions, etc
- Once in a while, I will be posting conference talk slides, longer form articles (such as this one), and other content as subscriber-only