DEV Community

loading...
Cover image for Ruby vs Elixir - FizzBuzz

Ruby vs Elixir - FizzBuzz

Josh Hadik
My day job is designing and developing websites, but in my off time, I like to build all kinds of things, including Ruby gems, iPhone apps, and Alexa skills.
Updated on ・6 min read

I've been working through a lot of common interview questions recently as part of my daily reps, something I do everyday to force me to learn constantly and hone my craft. I decided rather than solve each problem in just my native language of Ruby, I'd solve each problem twice, once in Ruby and once in Elixir. You can read a little bit more about why I chose to do that here.

I figured while I'm doing that, it might be interesting to write about some of the key differences I notice between the two. So that's what this is, a quick breakdown of some key differences, not a step-by-step guide of how I came to the solutions.

In these examples, I tried my best to use each language in the truest form possible, meaning in Ruby I followed a strict object oriented approach and relied primarily on objects, mutation, instance variables, and iteration, and in Elixir I followed a strict functional approach and relied mainly on functions, piping, pattern matching, and recursion.

I also tried my best to make the two solutions read as similarly as possible by using similar function names, variable names, and general order of operations.

Ruby FizzBuzz

# ruby
class FizzBuzz
  def initialize(max)
    @max = max
    @fizz_buzz_array = []
  end

  def play
    populate_fizz_buzz_array
    display_fizz_buzz
  end

  private

  def populate_fizz_buzz_array
    1.upto(@max).each do |num|
      value = get_fizz_buzz_value(num)
      add_value_to_fizz_buzz_array(value)
    end
  end

  def get_fizz_buzz_value(num)
    if num % 15 == 0
      "FizzBuzz"
    elsif num % 3 == 0
      "Fizz"
    elsif num % 5 == 0
      "Buzz"
    else
      num.to_s
    end
  end

  def add_value_to_fizz_buzz_array(value)
    @fizz_buzz_array << value
  end

  def display_fizz_buzz
    puts @fizz_buzz_array
  end
end

## USAGE

fizzbuzz = FizzBuzz.new(15)
fizzbuzz.play

# 1
# 2
# Fizz
# ...

Elixir FizzBuzz

# elixir
defmodule FizzBuzz do
  def play(max) do
    max
    |> create_fizz_buzz_list()
    |> display_fizz_buzz()
  end

  defp create_fizz_buzz_list(list, 0), do: list
  defp create_fizz_buzz_list(list \\ [], num) do
    num
    |> get_fizz_buzz_value()
    |> add_value_to_list(list)
    |> create_fizz_buzz_list(num - 1)
  end

  defp get_fizz_buzz_value(num) do
    cond do
      rem(num, 15) == 0 -> "FizzBuzz"
      rem(num, 3) == 0 -> "Fizz"
      rem(num, 5) == 0 -> "Buzz"
      true -> to_string(num)
    end
  end

  defp add_value_to_list(value, list) do
    [value | list]
  end

  defp display_fizz_buzz([]), do: :ok
  defp display_fizz_buzz([head | tail]) do
    IO.puts(head)
    display_fizz_buzz(tail)
  end
end

## USAGE

FizzBuzz.play(15)

# 1
# 2
# Fizz
# ...

Things I noticed

1. Elixir encourages declarative programming

Take look at 'create_fizz_buzz_list' and 'populate_fizz_buzz_array'

# ruby
def populate_fizz_buzz_array
  1.upto(@max).each do |num|
    value = get_fizz_buzz_value(num)
    add_value_to_fizz_buzz_array(value)
  end
end
# elixir
defp create_fizz_buzz_list(list \\ [], num) do
  num
  |> get_fizz_buzz_value()
  |> add_value_to_list(list)
  |> create_fizz_buzz_list(num - 1)
end

In the Ruby version, unless your an expert at the language, you almost need to take a minute to examine the code just to make sense of what's going on, whereas the Elixir counterpart almost reads like a book (all though the final line might be a bit confusing.)

Of course, I could have written Ruby to be a bit more declarative, but the point of this was to follow common patterns and write code the way I actually would in the real world, and I think Elixir does a better job at encouraging the use of a more declarative style of coding.

2. Elixir encourages better abstraction

Look at the function/method where I add the fizz buzz value to the collection:

# ruby
def add_value_to_fizz_buzz_array(value)
  @fizz_buzz_array << value
end
# elixir
defp add_value_to_list(value, list) do
  [value | list]
end

Notice how in Ruby, I called it "add_value_to_fizz_buzz_array," but in Elixir, I dropped the "fizz_buzz" and just called it "add_value_to_list." This was very intentional. In Ruby, the method call is adding the value only to the predefined fizz_buzz_array, however, the Elixir function is a bit more generic and adds the element to whatever list the function is called with.

This means that the Elixir function is quite a bit more flexible than the Ruby version, and could easily be abstracted into some other module and reused throughout the code.

Again, I know I could have written Ruby with a more functional approach and achieved the same behavior, but the fact is, in Ruby, the standard way to make changes is to do exactly what I did and manipulate instance state internally through the use of methods.

3. Elixir works better "backwards"

Because lists in Elixir are actually linked lists and not indexed arrays like their counterpart in Ruby, I had to take a different approach when interacting with them.

In Ruby it's cheap and easy to append to the end of an array so that's become the defacto way to add elements to an array, which is exactly how I did it in this example.

On the other hand, in Elixir it's easier and requires much less processing power to add elements to the beginning of a list, due mainly to the way linked lists work.

That alone isn't very noteworthy, but what I found interesting about it was it changed the entire direction I worked through the numbers in. In Ruby, because I was adding to the end of the array, it made the most sense to start at 1 and iterate up to 15:

# ruby
1.upto(@max).each do |num|
  # ...
end

However, in Elixir, because I was adding to the beginning of the list, it made more sense to start at 15 and recursively work my way down to 1.

# elixir
create_fizz_buzz_list(num - 1)

4. Ruby is more likely to cause unexpected side effects

In a functional language like Elixir, if something works once, it works twice. There's almost always a one-to-one relationship between input and output. With object oriented languages on the other hand, you have to be a little bit more careful.

Notice how I'm never clearing the @fizz_buzz_array variable in Ruby, just adding new elements to it. If I try to 'play' a specific instance of FizzBuzz one time, it works like a charm. But what happens if I try to play the same instance twice?

# ruby
fizzbuzz = FizzBuzz.new(3)
fizzbuzz.play

# 1
# 2
# Fizz

fizzbuzz.play

# 1
# 2
# Fizz
# 1
# 2
# Fizz

This is a glitch you probably wouldn't think to test, and something you might not even realize is happening unless you have a deep understanding of Ruby and the Object Oriented programming pattern. This glitch is easily solvable. I could clear the array in every time I populate it, or I could move the entire "populate_fizz_buzz_array" method call from the play function to the initialize function so the array only gets populated once, but I decided to leave it in as an example of how in OOP it's often times very easy to have less-than-ideal side effects slip under the radar.

I don't hate Ruby

When I came up with the “things I noticed” list, it was after I had already written all of the code. I tried to write honest stream of consciousness of what I noticed while I looked back over the code. I know a lot of these points seem like I'm really hyping up Elixir and shitting on Ruby, but that wasn't my intention going in, that was just the honest list of the things that stood out to me when I compared the code.

If it seems like I have a strong bias towards Elixir, I think that's because Elixir is the new, exciting language that I've only recently started working with, whereas Ruby is the old, almost boring language that I've worked with for years. Elixir to is the 'greener grass on the other side,' so right now I know I'm prone to seeing only the good and not the bad. I'm sure once I become a more familiar with Elixir I'll have a more balanced view of the good and the bad of both, but for now, I definitely find myself drawn to the way things are done in Elixir.

What do you think?

Does anything else stand out to you about the difference between the two solutions? Which version do you prefer?

Thanks for Reading!

If you found this article interesting or informative and want to see more articles like it, let me know! And feel free to let me know what you think would be a good topic for my next Ruby vs Elixir article! (Fibonacci? Factorials? Something Else?)

Discussion (6)

Collapse
art4ride profile image
Vladimir • Edited

Nice article, thanks for sharing! Quick suggestion:

You can get to pattern matching power in this place:

  defp get_fizz_buzz_value(num) do
    cond do
      rem(num, 15) == 0 -> "FizzBuzz"
      rem(num, 3) == 0 -> "Fizz"
      rem(num, 5) == 0 -> "Buzz"
      true -> to_string(num)
    end
  end
Enter fullscreen mode Exit fullscreen mode

->

  defp get_fizz_buzz_value(num) when rem(num, 15) == 0, do: "FizzBuzz"
  defp get_fizz_buzz_value(num) when rem(num, 3) == 0, do: "Fizz"
  defp get_fizz_buzz_value(num) when rem(num, 5) == 0, do: "Buzz"
  defp get_fizz_buzz_value(num), do: to_string(num)
Enter fullscreen mode Exit fullscreen mode

Might be even nicer to skip rem(,15) in favor of rem(,3) and 5 together separately, but it will also affect the Ruby code then

Collapse
joshhadik profile image
Josh Hadik Author

I like this... a lot!

Collapse
jeffweiss profile image
Jeff Weiss

Equivalent-ish, Elixir code golf solution:

1..31|>Enum.map(fn n when rem(n,15)==0->"FizzBuzz";n when rem(n,3)==0->"Fizz";n when rem(n,5)==0->"Buzz";n->n end)|>IO.inspect
Collapse
jwollner5 profile image
John 'BBQ' Wollner

I played w/ ruby 10y+ ago and enjoyed it, but never pursued it. You've piqued my interest in elixer though - have to do some reading on that. Seems like the syntax would make it a fit general purpose

Collapse
joshhadik profile image
Josh Hadik Author

Haha always love a good one liner!