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
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
Let's see if that is the expected result using #p
:
p 40 + 2
the output would read:
42
So, what was the problem with the first example?
puts typeof("Hello!") # => String
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
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
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
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"
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 moduleKernel
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
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
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
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
Let's follow the code path and continue with the implementation of Object#to_s(io : IO)
:
abstract def to_s(io : IO) : Nil
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
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'sstdlib
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
The output will read:
#<A:0x7f25e8f28ea0>
#<A:0x7f25e8f28ea0 @foo="Foo", @bar="Bar">
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)