Why I love Ruby: string representation
Tags: ruby, crystal
Published: 2022-02-18 12:39:31

---
title: Why I love Ruby: string representation
published: true
description: 
tags: ruby, crystal
//cover_image: https://direct_url_to_image.jpg
series: Why I love Ruby
---

# String representation

In Ruby every object responds to `to_s` with a good default. There's also `inspect` which usually reveals the internal structure of an object.

For example:

```ruby
class Point
  def initialize(x, y)
    @x = x
    @y = y
  end
end

point = Point.new(1, 2)
point.to_s # => #<Point:0x00007fab148d55d8>
point.inspect # => #<Point:0x00007fab148d55d8 @x=1, @y=2>
```

Okay, maybe `to_s` isn't that useful and `inspect` is much more useful, but it's nice that they are there.

The nice thing about `inspect` is that they also takes potential cycles into account:

```ruby
class Person
  def initialize(name)
    @name = name
    @sibilings = []
  end

  def add_sibiling(person)
    @sibilings << person
  end
end

ary = Person.new("Ary")
gabriel = Person.new("Gabriel")
ary.add_sibiling(gabriel)
gabriel.add_sibiling(ary)

ary.inspect # => #<Person:0x00007fe670851160 @name="Ary", @sibilings=[#<Person:0x00007fe6708510c0 @name="Gabriel", @sibilings=[#<Person:0x00007fe670851160 ...>]>]>
gabriel # => #<Person:0x00007fe6708510c0 @name="Gabriel", @sibilings=[#<Person:0x00007fe670851160 @name="Ary", @sibilings=[#<Person:0x00007fe6708510c0 ...>]>]>
```

Not only it doesn't crash: it also shows the object ID of objects so you can know that when there's a cycle, what that object is.

Similar code works fine in Crystal too:

```crystal
class Person
  def initialize(@name : String)
    @sibilings = [] of Person
  end

  def add_sibiling(person)
    @sibilings << person
  end
end

ary = Person.new("Ary")
gabriel = Person.new("Gabriel")
ary.add_sibiling(gabriel)
gabriel.add_sibiling(ary)

ary.inspect # => #<Person:0x107d3aea0 @name="Ary", @sibilings=[#<Person:0x107d3ae60 @name="Gabriel", @sibilings=[#<Person:0x107d3aea0 ...>]>]>
gabriel.inspect # => #<Person:0x107d3ae60 @name="Gabriel", @sibilings=[#<Person:0x107d3aea0 @name="Ary", @sibilings=[#<Person:0x107d3ae60 ...>]>]>
```

The above output is a bit hard to read... so Ruby and Crystal allow you to pretty print objects, every type of object, right out of the box. Here's Ruby with the above value:

```ruby
require "pp"
pp ary
```

Output:

```
#<Person:0x00007ffad4076fd0
 @name="Ary",
 @sibilings=
  [#<Person:0x00007ffad404b8a8
    @name="Gabriel",
    @sibilings=[#<Person:0x00007ffad4076fd0 ...>]>]>
```

Here's Crystal:

```crystal
pp ary
```

Output:

```
#<Person:0x101418ea0
 @name="Ary",
 @sibilings=
  [#<Person:0x101418e60
    @name="Gabriel",
    @sibilings=[#<Person:0x101418ea0 ...>]>]>
```

In other languages this ability to easily inspect objects out of the box is not present, or is not great. At least in some languages all objects can be converted to a string, even if the output isn't very useful. But some languages doesn't do that... and I'll mention Haskell now, maybe because I'm using it at work, but I think this is also true in Rust.

In Haskell you can't turn any value into a string **by default**. The type has to implement the `Show` typeclass. In practice this means that if we have the `Point` type I mentioned above:

```
❯ 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> p1

<interactive>:8:1: error:
    • No instance for (Show Point) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it
```

You can't see what's `p1`. You again have to put `deriving (Show)` at the end of the declaration, or define a custom `show` function for the `Show` typeclass.

This might not sound like a lot to do, but when you are debugging code and you can't inspect objects and then you have to stop what you are doing and what you were thinking about, to go and open a file and add `deriving (Show)` in a lot of places just to see what's going on, it's not fun. It's less fun when those types aren't in your control and it's harder to add a `Show` for them. At the end of this process you end up thinking "should I leave these `deriving (Show)` or should I remove them now that I'm done with them", and it's where my question "why aren't these derived by default" comes to mind.

This is also when Ruby's goal, "developer happiness", comes to my mind. Doing all of the above isn't fun. In Ruby we don't have to do that, Ruby took care of that and we can just have fun solving more interesting problems.

That said, once you add `deriving (Show)`, Haskell works fine, and in general `show` is very well implemented for "standard library" types:

```
❯ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/  :? for help
Prelude> data Point = Point { x :: Int, y :: Int } deriving (Show)
Prelude> p1 = Point { x = 1, y = 2 }
Prelude> p1
Point {x = 1, y = 2}
Prelude> [p1]
[Point {x = 1, y = 2}]
Prelude> ["hello", "world"]
["hello","world"]
```

## Another language as an example: Go

Let's take a look at another language. I randomly chose Go because it's a relatively modern language, very popular, and there's a playground to try things out.

Here's an example from [Go's tour about arrays](https://go.dev/tour/moretypes/6):


```go
package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}
```

This is the output:

```
Hello World
[Hello World]
[2 3 5 7 11 13]
```

Let's compare it to the output of Ruby or Crystal. I'm using `p` here which invokes `inspect` on objects.

```ruby
a = ["Hello", "World"]
p a[0], a[1]
p a

primes = [2, 3, 5, 7, 11, 13]
p primes
```

Here's the output:

```
"Hello"
"World"
["Hello", "World"]
[2, 3, 5, 7, 11, 13]
```

The first thing to note is that Ruby and Crystal put quotes around strings (well, this isn't the case if you call `to_s` on a string, only `inspect`.) Then note that array contents use `inspect`. This makes it possible to know that the first array has two strings. In Go it's not clear: are there two elements, "Hello" and "World", or is it just one string "Hello World"?

Another nice thing is that you can copy that array output from Ruby and Crystal, paste it into a program and it will work. This isn't generally true, but it works really well for primitive types, arrays and hashes, which are used a lot! In Go this isn't true. There aren't even commas! I don't know why.

Now, I'm sure there's a way to show arrays or strings in Go in a better way. For example in Java you can use `Arrays.toString`. But not having that as a default adds friction. It's not the most intuitive thing you would expect to happen.

Another thing is consistency and uniformity. In Go there's the [Stringer](https://pkg.go.dev/fmt@go1.17.7#Stringer) interface that defines the `String()` function to turn objects to a string. So what happens if we call `String()` on an array?

```go
package main

import "fmt"

func main() {
	a := [2]string{"Hello", "World"}
	fmt.Println(a.String())
}
```

We get a compile error:

```
./prog.go:7:15: a.String undefined (type [2]string has no field or method String)
```

So it seems only `fmt` knows how to turn arrays into strings, and we have to rely on this package for that, but for other types we probably should use `String()`.

It's those things that rarely exist in Ruby that I really appreciate. When something works in one way in Ruby you understand it and think "well, I guess this also works for these other types, or in these contexts" and that's almost always true. When that's not the case in a language, it's the moment you start building a collection of exceptions in your head, and when you start looking for answers in StackOverflow.

# Coming up next...

I'll talk about [making a language your own](https://dev.to/asterite/why-i-love-ruby-part-3-2oin).