DEV Community

Cover image for From`# =>` to `#p`
Franciscello
Franciscello

Posted on

From`# =>` to `#p`

Sometimes writing documentation can lead us to discover some details of a language!

I opened a PR adding documentation about Generics with a variable number of type arguments in Crystal with some code examples using # =>.

Here's a simplified example:

puts typeof("Hello!") # => String
Enter fullscreen mode Exit fullscreen mode

But then I got a suggestion that read (broadly speaking): use # => next to an expression to show what the result of that said expression would be.

In other words: If we have the expression 40 + 2 then we can add a comment using # => showing the expected result:

40 + 2 # => 42
Enter fullscreen mode Exit fullscreen mode

Let's see if that is the expected result using #p:

p 40 + 2
Enter fullscreen mode Exit fullscreen mode

the output would read:

42
Enter fullscreen mode Exit fullscreen mode

So, what was the problem with the first example?

puts typeof("Hello!") # => String
Enter fullscreen mode Exit fullscreen mode

The suggestion continued: [...] #puts returns nil.

😲 So if #puts returns nil and # => shows the result of evaluating the expression, then the correct comment would be:

puts typeof("Hello!") # => nil
Enter fullscreen mode Exit fullscreen mode

Well ... this is not what we want! 🥲

Documenting with # =>

The last part of the suggestion read: [...] Also, #p uses #inspect and returns its argument.

So, maybe we can write:

p typeof("Hello!") # => String
Enter fullscreen mode Exit fullscreen mode

Technically speaking this is correct because #p not only prints String which is the value of typeof("Hello!") but also returns that said value.

It's correct but ... we don't need to use #p. We only need # => for showing the expected value, like this:

typeof("Hello!") # => String
Enter fullscreen mode Exit fullscreen mode

And now the code is well documented! 🤓🎉

Another example

Just to reinforce what we've just learned, let's see another example. This one is taken right from the documentation:

a = 1
b = 2
"sum: #{a} + #{b} = #{a + b}" # => "sum: 1 + 2 = 3"
Enter fullscreen mode Exit fullscreen mode

In this example we are defining a string literal using interpolation (which let us embed expressions) and so with the use of # => we are showing the resulting string.


Up to this point, we have learned how to document lines of code using comments with # =>. Great!

And all this led us to see the difference between #p and #puts ... so maybe we can take a look at the implementation of these two methods? 🤔 ... yeah! Let's do that! 🤓🎉

But before doing that ...

📚 Did you know?
Methods #puts and #p also exist in Ruby.
They are implemented in module Kernel and with the same behaviour.
We can read the implementations for #puts and #p written in C in the documentation itself.

And now ... 🥁

#puts vs #p under the hood 🔬

#puts

Here is the source code for #puts:

def puts(*objects) : Nil
  STDOUT.puts *objects
end
Enter fullscreen mode Exit fullscreen mode

As we can see it forwards the responsibility to IO#puts. Let's see the implementation:

def puts(string : String) : Nil
  self << string
  puts unless string.ends_with?('\n')
  nil
end
Enter fullscreen mode Exit fullscreen mode

Great! The method writes the string and it returns nil (as we were already expecting).

#p

Now let's see the implementation of #p:

def p(object)
  object.inspect(STDOUT)
  puts
  object
end
Enter fullscreen mode Exit fullscreen mode

First, we may notice that the method returns (again, as we were expecting) the argument.

Then we may notice it forwards the "print responsibility" to Object#inspect(io : IO). Here is the source code:

def inspect(io : IO) : Nil
  to_s io
end
Enter fullscreen mode Exit fullscreen mode

Let's follow the code path and continue with the implementation of Object#to_s(io : IO):

abstract def to_s(io : IO) : Nil
Enter fullscreen mode Exit fullscreen mode

Ok, we've just found an abstract method.

Because we are trying to "print" a Class (remember p typeof("Hello!") # => String), let's see how Class#to_s(io : IO) is implemented:

def to_s(io : IO) : Nil
  io << {{ @type.name.stringify }}
end
Enter fullscreen mode Exit fullscreen mode

We can see Class#to_s outputs the Class string representation.

And we may read about the use of {{ }} in the macros docs 🤓

🤯 Did you notice?
We've just learned how the methods #puts and #p are implemented, and all the time we were reading code in Crystal.
Yes! Crystal's stdlib is written in Crystal itself, making it a lot more natural to inspect and learn how the language works under the hood. 🤩

Example

Here is another example that shows the difference between using #puts and #p:

class A
  def initialize
    @foo = "Foo"
    @bar = "Bar"
  end
end

a = A.new
puts a
p a
Enter fullscreen mode Exit fullscreen mode

The output will read:

#<A:0x7f25e8f28ea0>
#<A:0x7f25e8f28ea0 @foo="Foo", @bar="Bar">
Enter fullscreen mode Exit fullscreen mode

We can see that #p (which we now know uses #inspect) prints more information.

Farewell and see you later

Let's recap:

  • We have learned about documenting lines of code using # =>.
  • We have traveled through the implementation of #puts and #p.
  • And finally we have seen an example with the difference between using #puts and #p.

Hope you enjoyed it! 😃

Top comments (0)