Crystal: Object#puts

Created on 4 Aug 2018  路  7Comments  路  Source: crystal-lang/crystal

A little background: I'm a big fan of Elixir's pipe operator and would love to see it in Crystal, but I totally understand that it's been decided against in favor of method chaining. However, I think the standard library is a little lacking in terms of method chain-ability. For example, I find myself wanting to reopen the String class to add a chain-able puts method; something like this:

class String
  def puts
    puts(self)
  end
end

Because puts is in the top-level namespace, maybe it makes more sense to add a puts method to the Object class. I'd be more than happy to write up a Pull Request for this, but I thought it might warrant some discussion from other members of the community.

Most helpful comment

puts makes no sense for chaining purposes because it's always the last element in the chain.

You can of course continue discussing this specific topic for years, but it's not going to happen.

All 7 comments

Why would foo.puts be any improvement over puts foo?

puts is also a common method on any IO type and foo.puts would mean printing an empty line to the IO. So this proposal would add unnecessary ambiguity.

I would say this would be more of a case for something along the lines of refinements than altering String to have a puts method (which by the way returns nil, so that implementation wouldn't be chainable anyway 馃槢 lol)

With refinements, you could do

refine String do
  def puts
    STDOUT.puts(self)
    self
  end
end

Now, with that being said, I personally don't like refinements (in ruby, that is), and do not advocate for the addition in to crystal.

Why would foo.puts be any improvement over puts foo?

Adding a puts method (or some other name) to all objects would make method chains more readable. Here's a trivial example:

# This way doesn't preserve the flow of data. 
puts "hello"
  .upcase
  .reverse

# This way does preserve the flow
"hello"
  .upcase
  .reverse
  .puts

# This can currently be accomplished with tap, but it's extra cumbersome
"hello"
  .upcase
  .reverse
  .tap { |x| puts x }

puts is also a common method on any IO type

Oh! You're totally right. That slipped my mind. I suppose naming it something else wouldn't make much sense. What if instead of being on Object, it was just on String? That way the method chaining can still happen, it would just require a to_s to be in between.

along the lines of refinements

Whoa, I didn't know refinements existed! That's a pretty neat way to handle monkey patching without breaking things. Has there been any consideration for Crystal to have refinement capabilities?

that implementation wouldn't be chainable anyway

Right! In my real world usage of this that I've found, I end up not needing to chain past outputting the string, so I never end up returning self. If this was going to end up in the standard library, I think returning self like in your sample would be a good idea.

puts makes no sense for chaining purposes because it's always the last element in the chain.

You can of course continue discussing this specific topic for years, but it's not going to happen.

I don't particularly want to chain _from_ puts, but rather to be able to add puts to the end of a chain

You can always add it at the beginning. It's readable, just put a space before whatever you want to print.

You can puts anything, not just string. That's why adding puts just to String would be inconsistent.

This change adds nothing in terms of usability or performance. It would just add yet another way to do something, and we try to avoid multiple ways of doing things in Crystal.

I don't decide stuff here, but it's very unlikely this will be incorporated in the standard library. You can always monkey patch it in your projects or make a shard, though.

we try to avoid multiple ways of doing things in Crystal

That makes a lot of sense. I'll go ahead and close this issue.

You can always monkey patch it in your projects

The main reason I wouldn't want to do this is because it affects the global namespace that other shards might use. If we had something like Ruby's refinements, that wouldn't be a problem. (That's a totally different issue though :smile: )

Was this page helpful?
0 / 5 - 0 ratings