Why I love Ruby: equality
Tags: ruby, crystal
Published: 2022-02-17 13:42:18

---
title: Why I love Ruby: equality
published: true
description: Things I really like about Ruby that I wanted to have in Crystal
tags: ruby, crystal
series: Why I love Ruby
---

In this series of posts I will list the things that I always loved about Ruby. Some of these work differently in other languages or they don't exist at all. I will try to give some comparisons when possible. But in general my feeling is that Ruby always does the most intuitive thing a programmer would expect, and I love that! And it's what I tried to preserve in Crystal.

# Comparing objects

In Ruby you can compare any two objects and Ruby will do the most intuitive thing.

```ruby
1 == 1           # => true
1 == 1.0         # => true (in math too!)
[1, 2] == [1, 2] # => true
```

The last one about comparing arrays is important: not every language does this. For instance, if you compare two arrays in Java or C#, you only get `true` if they are the **same** array, that is, the same reference to memory. Then if you search "how to compare two arrays in Java/C#" you will get lots of answers in StackOverflow trying to answer this. In Java you can use `Arrays.equals`, but that doesn't do a deep comparison, so you also have `Arrays.deepEquals`.

The tricky part about array comparison is that you might end up having an array with an object that in turn has a reference to the same array, leading to a cycle. When trying to compare them, if you don't take cycles into account, the program will stack overflow, or crash. You don't want that in your program!

Well, in Ruby **it just works**. It will compare two arrays by calling `==` on each element, and it will also take care of cycles. The following works:

```ruby
a = []
a << a # => => [[...]]

a == a # => true
```

All of the above work just fine in Crystal, except that for the recursive array definition. I _thought_ it worked fine in Crystal but it seems it also leads to a stack overflow. But [it's something we'll eventually fix](https://github.com/crystal-lang/crystal/issues/11837).

In Ruby you can also compare `Hash`, `Set`, and many other types, where the definition of equality is the obvious one, and the same is true in Crystal.

## Equality everywhere

Another nice thing about equality in Ruby is that you get sensible defaults, and you can compare any two objects out of the box. For example, Ruby has a [Struct](https://ruby-doc.org/core-3.0.1/Struct.html) type that's used to pack some values together, kind of like a quick definition of an object with some properties:

```ruby
Point = Struct.new(:x, :y)

p1 = Point.new(1, 2)
p2 = Point.new(1, 2)
p1 == p2 # => true
```

In Crystal you can also define structs, and the default comparison just delegates to comparing the fields:

```crystal
struct Point
  def initialize(@x : Int32, @y : Int32)
  end
end

p1 = Point.new(1, 2)
p2 = Point.new(1, 2)
p1 == p2 # => true
```

This all sounds very intuitive. Why would a language **not** do that? Well, I don't know. Take for example Haskell, where these types of structs are really common:

```
❯ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/  :? for help
Prelude> data Point = Point { x :: Int, y :: Int }
Prelude> p1 = Point { x = 1, y = 2 }
Prelude> p2 = Point { x = 1, y = 2 }
Prelude> p1 == p2

<interactive>:4:1: error:
    • No instance for (Eq Point) arising from a use of ‘==’
    • In the expression: p1 == p2
      In an equation for ‘it’: it = p1 == p2
```

Haskell gives a compile-time error because `==` is not defined for Point. We need to define one. And we can use a magic `deriving` to let the compiler do the obvious thing:

```
❯ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/  :? for help
Prelude> data Point = Point { x :: Int, y :: Int } deriving (Eq)
Prelude> p1 = Point { x = 1, y = 2 }
Prelude> p2 = Point { x = 1, y = 2 }
Prelude> p1 == p2
True
```

Now, this last point is controversial (maybe this entire blog post is controversial!) Someone might say:

> But what if you didn't want this comparison by default? It's dangerous! Then you would have a bug in your code. It's better to always think about these cases and make sure the equality you want is really that one.

That's fine. My answer to that is that you want to have tests to verify that you code works in the way you want it. Compile-time safety is good, but it doesn't prove behavior.

# Coming up next...

In the next part I'll talk about [Ruby's `to_s` method](https://dev.to/asterite/why-i-love-ruby-part-2-102j).