Almah
Hey everyone!
I’m working my way into elixir and loving it so far. However, coming from an OOP background with Java/Kotlin, I don’t think I’m writing intuitive or “elixir-style” code. I’d love some feedback on my Toy Robot solution which you can find here, but I’ll paste the relevant bits I think need to be improved.
What is the Toy Robot Challenge? Here is a CodeReview question that has the entire Toy Robot brief in case you’re curious: https://codereview.stackexchange.com/questions/236006/toy-robot-simulator
I’ve tried to implement a version of the Command pattern, but I don’t think I did it very well. Every command has a Behavior they… inherit? from:
(I have omitted my error handling for brevity)
defmodule ToyRobot.Commands.Command do
@type state :: :uninitialized | :initialized
@type t() :: {state(), Board.t(), Robot.t()}
@callback execute(any(), t()) :: t()
end
defmodule ToyRobot.Commands.PlaceCommand do
@behaviour ToyRobot.Commands.Command
@impl ToyRobot.Commands.Command
def execute(%{x: x, y: y, facing: facing}, {:uninitialized, board, _robot}) do
{:initialized, board, %ToyRobot.Robot{x: x, y: y, facing: facing}}
end
@impl ToyRobot.Commands.Command
def execute(%{}, {:initialized, _, _} = state) do
state
end
end
defmodule ToyRobot.Commands.LeftCommand do
@behaviour ToyRobot.Commands.Command
@impl ToyRobot.Commands.Command
def execute(%{}, {:initialized, board, robot}) do
{:initialized, board, ToyRobot.Robot.left(robot)}
end
# Error handling
end
Each command is then sent to a GenServer which contains the state information about the world:
defmodule ToyRobot.Boundary.World do
use GenServer
def start_link(options \\ []) do
GenServer.start_link(__MODULE__, {:uninitialized, %ToyRobot.Board{width: 5, height: 5}, nil}, options)
end
def get_world(manager \\ __MODULE__) do
GenServer.call(manager, {:get_world})
end
def execute_command(manager \\ __MODULE__, {name, command_args, callback_fn}) do
GenServer.call(manager, {:execute, name, command_args, callback_fn})
end
def init(world) do
{:ok, world}
end
def handle_call({:initialize}, _from, world) do
{:reply, :ok, world}
end
def handle_call({:get_world}, _from, world) do
{:reply, {:ok, world}, world}
end
def handle_call({:execute, _name, command_args, callback_fn}, _from, world) do
{:reply, :ok, callback_fn.(command_args, world)}
end
end
As you can see, a command is created and contains:
- Name of the command
- Arguments to invoke the command
- The command function that will operate on its provided args and the world state
The GenServer contains the world state and is the one to execute the command by invoking the command function with callback_fn.(command_args, world)
Finally, my CommandParser would parse a string to a command which can then be executed. The code is unfinished, but you can roughly see what that should be like here:
defmodule ToyRobot.CommandParser do
@moduledoc """
Parses commands from a file or command line.
"""
alias ToyRobot.Commands.{PlaceCommand, MoveCommand, LeftCommand, RightCommand, ReportCommand, TeleportCommand}
def parse("PLACE " <> args) do
[x, y, facing] = String.split(args, ",")
{:place, %{x: 1, y: 1, facing: :north}, &PlaceCommand.execute/2}
end
def parse("MOVE") do
{:move, %{}, &MoveCommand.execute/2}
end
# Other commands
end
I have a feeling that my Command-code is very, well, Java-oriented and I’m doing too much work for what I’m trying to achieve. I just don’t know how to do it better yet. Any help would be greatly appreciated! ![]()
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 6 of 6 Posts
christhekeele
Could you share a link to the problem you are solving? I cannot find one in the repository you provide, and what you are trying to solve (with or without a Elixirish version of the command pattern) is unclear.
Almah
Ah, my apologies. I jumped the gun a little and forgot to give context. The Toy Robot challenge can be found here (https://codereview.stackexchange.com/questions/236006/toy-robot-simulator) but to summarize:
The Command Pattern is a way to abstract behavior (the commands that act on the robot) and encapsulate all data necessary to complete the command in one place. I’m unaware of an elixir-based solution, I’m sorry. It might also be that the pattern is expressed naturally in functional code and doesn’t need to be “patternized.”
benwilson512
Right in some sense the “Command Pattern” from OO is just about passing around a data object, and in a functional language all that you pass around is data, so it doesn’t really need a formal name.
The normal thing to do in Elixir here would be to have some sort of
%Command{}struct that contained the information a command needed. In reviewing the prompt though a struct is almost more complicated than you need as there just aren’t that many instructions, you could probably just get away with a handful of atoms and maybe a tuple for{:place, x, y}.Probably the other big question is whether the command should itself have a callback to execute itself, or whether the world should execute the command. If you want to stick with having the command execute itself then that’s fine, but the world needs to run a validity check afterward on the resulting
world.It shouldn’t be up to the command to validate the world, the world should validate the world.al2o3cr
Here’s a version that’s in Elixir but feels more Erlang-y, in particular:
stepaccumulates “commands” is modeled aftergen_statem’s “actions”. This keeps side-effects like printing to stdout out of functions likerunI find this style useful for small one-off tasks like Advent of Code; bigger and longer-lived code can benefit from investing in more-complex features:
to_dir/from_dirseparatelyBut those approaches take longer to write
so stay tuned.
christhekeele
This feels like a fun project to tackle with a CLI app, with a graphical interface!
So, I build a Elixir project seed for it, if people want to try solving it that way: challenges/toy-robot at latest · christhekeele/challenges · GitHub
More instructions on how to clone the seed and view my attempt at solving here.
christhekeele
And my first take on a solution here: Comparing latest...toy-robot · christhekeele/challenges · GitHub