crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.42k stars 1.62k forks source link

Please add 'respond_to?' as synonym for 'responds_to?' #4256

Closed drosehn closed 7 years ago

drosehn commented 7 years ago

It would be helpful if crystal supported Object.respond_to?() in addition to Object.responds_to?().

I do prefer Object.responds_to?() as the name, but this is one (pseudo-)method name where it's very useful to have the exact same name available when writing code for both ruby and crystal. And if this one method exists in both ruby and crystal, then programmers can use this method to handle other differences between ruby and crystal.

RX14 commented 7 years ago

Crystal doesn't do aliases: https://github.com/crystal-lang/crystal/issues/1179#issuecomment-131267190

We also don't aim to be ruby compatible. Trying to write scripts which run in both is going to be a waste of time.

drosehn commented 7 years ago

I'm not looking for many aliases, I'm asking for this one pseudo-method to exist with two names. One pseudo-method. I don't expect to write full-blown programs in both languages, but it is useful if one could do that when writing benchmark programs to demonstrate how much faster crystal can be over ruby.

sevk commented 7 years ago

why not do this by your self ?

class Object
  def respond_to?
     self.responds_to?
  end
end
drosehn commented 7 years ago

Well, I do appreciate that crystal does not want to support the wide variety of method-aliases which have been added to ruby, so I did spend a few hours earlier today trying to come up with some code trickery which would do what I wanted. I wanted to use the exact same code in both crystal and ruby such that either respond_to? or responds_to would exist in both environments. Everything I tried, failed. It was only after those failed attempts that I wrote this request.

The above won't compile in crystal, but if I start to explain why I'll end up explaining 10 other things which also do not work. I'll admit that I am tempted to do that, but I suspect most people would find it tedious and boring.

FWIW, the most obvious issue with the above is that the "r2" methods take a parameter. The second challenge is that responds_to? in crystal takes a literal symbol as a parameter (such as :gets), and will not accept a variable name. There are additional challenges after those.

ysbaddaden commented 7 years ago

We won't introduce an alias for such a detail. What may be challenged is using respond_to? instead of responds_to? (same for includes?) to maybe avoid a confusion.

drosehn commented 7 years ago

Part of me would hate to see it changed, because responds_to? seems like "more correct english" to me.

But when it comes to writing code, the name of respond_to? works just as well as responds_to?, and has the advantage of making it easier to implement a few things (and I do realize it's only a few things). But I can't think of a situation where choosing responds_to? makes it easier to implement any thing. So if crystal needs to stick with a single name per feature, I think I'd vote for respond_to? and maybe include?.

Would it help to note that respond_to? is also one fewer character to type?

drosehn commented 7 years ago

... but I should probably think about it a bit more. Right now I have one specific situation where respond_to? would be useful, but such situations might be pretty rare.

drosehn commented 7 years ago

Well, the following code does compile and run successfully even if dumb does not implement gets, so doing tricks with respond_to? will work for objects in both crystal and ruby:

dumb = MyThing.new
puts dumb.responds_to?(:gets)
dumb.gets if dumb.responds_to?(:gets)

Crystal does not allow the same trick with responds_to? on a Class, but it turns out that ruby also does not allow that trick on a Class. (which surprised me)

drosehn commented 7 years ago

Looking at include? vs includes?, those are both standard methods on objects (not on classes), and the parameters are standard values (not literal symbols), so programmers can work around that difference if respond_to? is available in both languages. The following code works in both languages, if I first define responds_to? in ruby:

dumbstr = "This has dumb text"
puts dumbstr.includes?("dumb")  if dumbstr.responds_to?(:includes?)
puts dumbstr.include?("dumb")   if dumbstr.responds_to?(:include?)

So my vote to rename includes? is much weaker than renaming responds_to?

danielpclark commented 7 years ago

Well, the following code does compile and run successfully even if dumb does not implement gets

You can use the macro assert_responds_to to stop the compilation with an exception. To have that method behave the same way in both Ruby and Crystal you could write it as follows.

def assert_responds_to(obj, mthd)
  return true if obj.respond_to?(mthd)
  raise %{Expected #{obj} to respond to :"#{mthd}", not #{ obj }}
end
puts assert_responds_to(1, "+")
# => true

Crystal will use the macro version whereas Ruby will use the method definition here. In either case if gets is not defined on dumb (from your earlier example) the program will stop at the raised exception.

and will not accept a variable name.

This issue still remains for this use case.

drosehn commented 7 years ago

I'm confused by that. I want the compilation (and execution) to always succeed in both ruby and crystal. I'm trying to avoid errors, not guarantee them.

ezrast commented 7 years ago

Could you do something like this? r2_patch.rb:

class Object
  alias_method :responds_to?, :respond_to?
end

r2_patch.cr (empty file):

workaround.crrb:

require "./r2_patch"

puts(1.responds_to? :to_s)
puts(2.responds_to? :foo)

And then:

q@hoteldetective 02:48:48 /tmp$  ruby workaround.crrb 
true
false
q@hoteldetective 02:48:51 /tmp$  crystal workaround.crrb 
true
false

You could also leave out the require and the empty file and just invoke ruby with -r/path/to/r2_patch.

drosehn commented 7 years ago

The tactic of using require with two files in the current directory does work in some situations, but it has the downside that you now need three different files instead of having a single self-contained file.

There are two situations where I'd like both languages to have a single method-name for responds_to?: 1) For some programs where the main goal of the program is to benchmark ruby vs crystal. It's nicer if each benchmark can be a completely self-contained file. And these programs include comments like the following, so the user really does have everything they need in the single file even if they know nothing about ruby or crystal:

#  To run:
#  crystal run thisBench && echo ____ruby: && ruby thisBench

2) Or for a script/program which I want to use on multiple OS-platforms, where crystal is available on one platform but not all all the platforms. So I compile it on the platform which has crystal, but I scp the file(s) into something like /usr/local/bin on other platforms. And if you use something like require "./ruby-vs-crystal" in a script in /usr/local/bin, that will fail unless the user first does a cd to the directory the script is in. This is not convenient.

And the tactic of using require hits a different problem if you try to use a fully-qualified pathname for the file. For instance, if you copy both ruby-vs-crystal files into /tmp and then use require "/tmp/ruby-vs-crystal", it works in ruby but crystal gives the error of:

# crystal run test-RvsC.rb
Error in test-RvsC.rb:6: while requiring "/tmp/ruby-vs-crystal": can't find file '/tmp/ruby-vs-crystal' relative to '...current-directory...'

require "/tmp/ruby-vs-crystal"
^

( Maybe I should write that up as a separate github-issue? )

drosehn commented 7 years ago

You could also leave out the require and the empty file and just invoke ruby with -r/path/to/r2_patch.

This suggestion works nicer than the require tactic, but it also makes me think of a completely different tactic which might be very interesting and useful. But I'll need to do some testing first, and right now there is some other work which I really must focus on. Hopefully I'll get back to that testing by Monday. And if this new idea works, then it would address more issues than renaming responds_to? would address. ( Note that I really do prefer the spelling of responds_to? over respond_to?! )

refi64 commented 7 years ago

Maybe, for the limited use case of benchmarks and single scripts, you could just do something like:

function crruby() {
  local file="$1"
  shift
  ruby <(sed s/responds_to/respond_to/g "$shift")
}

Then you could just call crruby instead of ruby?

drosehn commented 7 years ago

The crruby() tactic would work fine if I were the only user of these things (as would many of the other suggestions), but I'd like something cleaner than that if I ever manage to release these programs to the public at large.

asterite commented 7 years ago

Crystal is not meant to make it easy to port or compare against Ruby programs. In the long run we'll remove all references to "Ruby", because it brings more issues than conveniences.

RX14 commented 7 years ago

I think that the current website compares Crystal to Ruby perfectly, by saying it's "slick as Ruby" and "Crystal’s syntax is heavily inspired by Ruby’s". I think the problem is that Ruby and Crystal are more related than say C# and Java, but less related than python2 and python3. I struggle to find a comparison between where Crystal is compared to Ruby, I just don't think there have been 2 well-known languages in this position before.

asterite commented 7 years ago

Well, Elixir borrows a lot from Ruby but they never mention Ruby, anywhere. And Elixir is quite different than in Ruby. Maybe the only things I can see that are similar are the do "keyword" (but there it's also different, like you have to write if ... do, defmodule ... do and IO.inspect(obj), but that's it. Then everything else is pretty much different.

I think Crystal should do the same: just ditch everything from Ruby that's there "because Ruby does it like that". I'm not sure I like the names "puts" and "gets", for example, or the many similarities in the names between Ruby and C. I'd probably also remove responds_to? and add the concept of interfaces, which is more general and useful. Maybe even rename Hash and so on. That way comparisons between Crystal and Ruby will stop, and people won't expect to find things similar to Ruby and be disappointed (or open issues) when it's not the case.

RX14 commented 7 years ago

I'm not sure how I feel about that. I think we already have "interfaces" enough with modules. Though I've never used responds_to? I don't see an argument for removing it. In general I'm not fond of C names, and in favour of departure from Ruby's stdlib, however I think that we should keep some common names from Ruby's stdlib (mostly gets and puts).

Moreover, I feel that making Crystal less like Ruby just to get away from the impression that Crystal is Ruby would introduce breaking changes for a trivial reason.

bew commented 7 years ago

I agree with @RX14, and I'd like to add that I hate non-straight-forward names, and I think ruby did a quite good job at this "find straight forward names for everything" job... I'm not sure it's a good decision to change every names just "to be different".

bcardiff commented 7 years ago

In order to offer a bit more of background information:

The issue with responds_to? is that it express that a method with that name exists, but says nothing regarding the type of the arguments / return value. So it is clear that after an affirmative responds_to? which are the methods (with full signature) the compiler can allow to be called, so now crystal allows any method, that might fail after.

In a type filter that starts with an .is_a?, the compiler has more trustful information.

danielpclark commented 7 years ago

I understand that being compared to Ruby too much can lead to too many issues being opened here but I don't think it's wise to avoid names like responds_to? and Hash just because of it.

There are already some big projects using Crystal and looking at Frost seeing how it hasn't been able to keep up with breaking changes it simply becomes incompatible from major language changes. Yes the language still has lots of big changes ahead before 1.0 but I agree that it would be nice to keep more of a stable core of features where feasible. So I agree with @bew and @RX14 on that.

If the changes are to adhere to some core values that we want for the language then go ahead and change it. I agree with @bcardiff that is_a? might be more ideal as the Null Object Pattern can help remove the need for having responds_to? (the pattern if used correctly won't need is_a? either).


I'm considering writing a book on Crystal. But I'll need to wait until the changes to it aren't so drastic.

drosehn commented 7 years ago

I expect that crystal will have an easier time of it once the multi-processing support is fully implemented. That gives crystal an impressive capability that ruby simply cannot match, so it becomes more obvious that it isn't just a compiler for ruby code.

drosehn commented 7 years ago

I think interfaces provide different capabilities than modules, but I've had very little sleep in the last 3 days so I'm not in good shape to organize my thoughts. I do think both features are useful, but they're not quite the same. Also, I'm not sure we'd want to continue this general-topic discussion on a specific-detail issue, where that issue is already closed.

RX14 commented 7 years ago

@drosehn interfaces can be thought of a special case of modules with only abstract defs.

drosehn commented 7 years ago

(disclaimer: I really am tired and my brain is not necessarily working) But isn't it true that interfaces can describe class-methods, and class methods don't really work the same way in modules?

BMorearty commented 6 years ago

One tangible benefit I've seen of the respond_to? and include? syntax is the ability to dynamically generate matchers that work well to construct English-like assertions in a test framework that uses expect or should syntax. For example,

expect(obj).to respond_to(:foo) # <= test framework converts this to a call to `.respond_to?`
expect(list).to include(bar) # <= test framework converts this to a call to `.include?`

These matchers can be generated on the fly without having custom code for them. If Crystal uses responds_to? and includes?, the test framework can't automatically generated such matchers.

donovanglover commented 6 years ago

You can use should contain as an alternative to includes?, like so:

string = "hello world"
string.should contain "hello"

array = [1, 2, 3]
array.should_not contain 5

I've personally never used responds_to? and find it confusing that an object can "respond to" anything at all.

chris-huxtable commented 6 years ago

Added respond_to? to extras.cr for those interested.

BMorearty commented 6 years ago

@GloverDonovan I may not have made my point clear. It was that the symmetry between the name of the assertion and the name of the underlying method has a couple of benefits:

  1. Assertions can be auto-generated from any method whose name ends with ?, without having to be hard-coded like should contain.
  2. It is easier for developers to comprehend the meaning of an assertion if it matches the name of the underlying method that it calls. E.g. should include calls include?, rather than should contain calls include?.

So I'm making a generalization about the naming pattern of all methods. I didn't mean to imply there's no way to make the assertion.

Regarding an object "responding to" anything at all, I can see why it seems like an odd term. The name comes from the original description of object-oriented programming, in which calling a method on an object is really sending a message to it. The object then responds to the message you sent. In those terms I guess it makes more sense.

thoran commented 4 years ago

It may be worthwhile to give consideration to any discussions around how that and similar decisions were made in Ruby. Presumably, we would be re-inventing the wheel by ignoring a possibly good rationale which already has a publicly accessible discussion. I found this: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/18951, which addresses that question, specifically in relation to the respond_to? cf. responds_to? question. There was one method in Ruby which had both the plural and the singular and the plural was subsequently removed.