From Ruby OOP to Elixir Functional by Example

Published: (December 1, 2025 at 11:00 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Ruby implementation

One of the most effective ways to learn functional programming is to convert the same features you’ve written in an OOP language to functional style. Below is a simple ToyRobot object in Ruby with three attributes (x, y, direction) and actions (move, turn left, turn right).

class ToyRobot
  attr_reader :x, :y, :direction
  DIRECTIONS = [:north, :east, :south, :west]
  BOUNDS = {x: 0..10, y: 0..10}

  def initialize
    @x = 0
    @y = 0
    @direction = :north
  end

  def place(x, y, direction)
    if DIRECTIONS.include?(direction)
      @x = x
      @y = y
      @direction = direction
    else
      raise "Invalid direction"
    end
  end

  def report
    {x: @x, y: @y, direction: @direction}
  end

  def move
    case @direction
    when :north then @y += 1
    when :east  then @x += 1
    when :south then @y -= 1
    when :west  then @x -= 1
    end

    if @x > BOUNDS[:x].max || @y > BOUNDS[:y].max
      raise "Out of bounds"
    end
  end

  def right
    case @direction
    when :north then @direction = :east
    when :east  then @direction = :south
    when :south then @direction = :west
    when :west  then @direction = :north
    end
  end

  def left
    # implement left
  end
end

Elixir implementation

Below is the same robot expressed in Elixir. If you’re new to Elixir, try implementing it yourself before looking at the code.

defmodule ToyRobot do
  @directions [:north, :east, :south, :west]
  @bounds_x 0..20
  @bounds_y 0..20

  defstruct x: 0, y: 0, direction: :east

  def new() do
    {:ok, robot} = place(0, 0, :east)
    robot
  end

  def place(_x, _y, direction) when direction not in @directions do
    {:error, :invalid_direction}
  end

  def place(x, y, _direction) when x not in @bounds_x or y not in @bounds_y do
    {:error, :out_of_bounds}
  end

  def place(x, y, direction) do
    {:ok, %ToyRobot{x: x, y: y, direction: direction}}
  end

  def report(%ToyRobot{x: x, y: y, direction: direction}) do
    {x, y, direction}
  end

  def right(%ToyRobot{direction: direction} = robot) do
    new_direction =
      case direction do
        :north -> :east
        :east  -> :south
        :south -> :west
        :west  -> :north
      end

    %ToyRobot{robot | direction: new_direction}
  end

  def left(%ToyRobot{direction: direction} = robot) do
    # implement right
  end

  def move(%ToyRobot{x: x, y: y, direction: direction} = robot) do
    {new_x, new_y} =
      case direction do
        :north -> {x, y - 1}
        :east  -> {x + 1, y}
        :south -> {x, y + 1}
        :west  -> {x - 1, y}
      end

    if new_x in @bounds_x and new_y in @bounds_y do
      %ToyRobot{robot | x: new_x, y: new_y}
    else
      robot
    end
  end
end

Key Differences

Method vs Function

  • Ruby (OOP): Methods belong to a class and operate on the object’s internal state.

    robot = ToyRobot.new
    robot.move
    robot.left
  • Elixir (Functional): Modules group functions; you pass the data explicitly.

    robot = ToyRobot.new()
    ToyRobot.move(robot)
    ToyRobot.left(robot)

In Elixir, every required piece of data must be supplied as an argument, whereas Ruby methods can access the object’s state implicitly.

Mutable vs Immutable

  • Ruby: Methods mutate the object’s state.

    robot = ToyRobot.new
    robot.move   # @x, @y change
    robot.report # => {x: 0, y: 1, direction: :north}
  • Elixir: Data structures are immutable; functions return new structs.

    robot = ToyRobot.new()
    # => %ToyRobot{x: 0, y: 0, direction: :east}
    
    new_robot = ToyRobot.move(robot)
    # => %ToyRobot{x: 1, y: 0, direction: :east}
    
    robot
    # => %ToyRobot{x: 0, y: 0, direction: :east}  # unchanged

Elixir’s pipe operator (|>) makes chaining transformations concise:

robot = ToyRobot.new()
|> ToyRobot.move()
|> ToyRobot.move()
|> ToyRobot.right()
|> ToyRobot.left()

Conclusion

The fundamental difference lies in how state is handled:

  • Ruby (OOP) – “The object changes its own state.”
  • Elixir (Functional) – “The function receives data and returns new data.”
Back to Blog

Related posts

Read more »

Functional Quadtrees

Article URL: https://lbjgruppen.com/en/posts/functional-quadtree-clojure Comments URL: https://news.ycombinator.com/item?id=46147341 Points: 12 Comments: 1...

Java OOPS Concepts

!Forem Logohttps://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%...