galori / swift-ruby

Bring some cool features from Swift to Ruby
28 stars 2 forks source link

Why does this feature exist? And how does one deal with optionals for methods already ending with a question mark? #2

Open jordaaash opened 10 years ago

jordaaash commented 10 years ago

The benefits of ActiveSupport's use of #try with a symbol for handling this feature are many.

First and foremost, there is already a very well-established Ruby convention for question mark methods, which are methods that (usually) take no arguments, return a boolean value, and are usually idempotent -- they don't change anything about the object's state when called, they only report it. This is in fact so common it's baked into basic objects for methods like #nil?, #empty?, and many, many others, as well as countless libraries that already use the convention.

By ignoring this convention, swift-ruby creates ambiguity for a questionable benefit that is already handled quite elegantly by #try, and it does so in a way that prevents its use in obvious special cases.

When I call #active? on my object, am I perhaps checking the state of active (idempotent) or am I actually calling active on the object, perhaps changing the state (not idempotent) of the object?

Let's assume (as swift-ruby does) that it's the latter. Even if I choose to make this assumption for my object, since I can't predict what classes may subclass said object, how can I know someone won't implement an #active? method for checking it's "active" state later, thereby breaking any code (including external code) that used swift-ruby's active? to call it?

Preventing this kind of ambiguity is what conventions are for. Ignoring the fact that it's pretty reckless to override #method_missing on Object in code intended for a library or language feature, and the performance issues that arise from that choice, it's not only unclear what you're doing when you call #foo?, but it can't even be relied upon to work in all cases.

To illustrate, let's further assume that I actually do have another declared method on my object, #foo? If I wanted to call this object with swift-ruby if it exists and ignore it otherwise (like I would with object.try(:foo?), I can't do that because in swift-ruby, that construct is object.foo?? which is not valid syntax and will presumably error.

When I see code like object.try(:foo?) and active?, it's quite apparent from reading what the developer intended. If you intend to port Swift features to Ruby, features that are basically just syntactical sugar, I think it should be done with more regard for the nature of the Ruby language and its conventions.

Ruby already has elegant solutions to problems that Swift's Optionals purport to solve, such as default values when setting a variable (using ||= for example), and especially the #try feature in question. This feature being proposed creates obvious new problems without having any clear benefit. I don't doubt that Swift will be much more pleasant to code in that Objective C, but that doesn't mean that Ruby has imminent need of borrowing its language constructs.

swistak35 commented 10 years ago

Reading the description of the repository, I guess you are taking it too serious - I think it's rather "check out what cool things we can do" instead of "this will be official and the only one implementation of swift features" kind of repository.

I can't do that because in swift-ruby, that construct is object.foo?? which is not valid syntax and will presumably error

It's actually not an error, but the beginning ternary operator.

Examples of methods with question mark:

[9] pry(main)> class Foo; def bar; puts "bar"; end; def bar?; puts "bar?"; end; end;
[10] pry(main)> f = Foo.new
=> #<Foo:0x000000019bd6a0>
[11] pry(main)> f.bar
bar
=> nil
[12] pry(main)> f.bar?
bar?
=> nil
[13] pry(main)> f.bar??
[13] pry(main)* 
[14] pry(main)> f.bar?? 0 : 1
bar?
=> 1

Anyway, ? is prettier than try(...), but one can write similar code like in this repo, but with prefix character (not necessarily ?), rather than suffix.

jordaaash commented 10 years ago

? may be prettier than try, but that doesn't make it more useful. I'd still argue that it's less explicit, less descriptive, less flexible, and more ambiguous. The point that I was making is that many of Swift's features are built around Optionals, which are really a specific way of introducing optional types and implicit null checks into a statically typed language and getting compile time safety instead of runtime null references.

But Ruby is not a statically typed language, nor is it a compiled language. In short, Ruby is not Swift. What makes it a cool feature for Swift is that it is cool for Swift. It doesn't make much sense to port language features, cool or otherwise, from one language to another where they don't make much sense in the target language.

galori commented 10 years ago

Great thread and great points.

I only skimmed the comments but I have a feeling I'm going to agree with most of your points.

But...it is an experiment, and a playground.

With a slim hope that if something cool comes out it will be useful - something like activesupport - adding a lot of c

galori commented 10 years ago

...continued

Cool features that make using ruby that much better.

I read that Rust's creator thought Swift is very close to Rust, but admitted that Rust borrowed a lot of the best ideas from other languages such as Haskell, Lua, SmallTalk, Ruby, Python.

This is what inspired this.

I was thinking "we can steal good ideas from other languages...we don't have to wait for the Ruby Core team to do it".

So I set off to see what was possible.

Maybe I didn't chose the best starting point :)

So - I'm off to read the Swift language guide to see what else looks good, and invite you to do the same, to see how far we can bend the ruby language itself.

(I think pretty far, but we shall see!)

galori commented 10 years ago

In fact...maybe this experiment should be broader and just be a "ruby+otherlaguages" project. For example Objective C's feature (likely swift too?) that sending a message to nil returns nil and doesn't raise an error is also nice. Can that be implemented in ruby?

jordaaash commented 10 years ago
class NilClass
  def method_missing (*)
    nil
  end
end

As with most attempts to silently swallow errors, if you think this is a good idea, you're gonna have a bad time.

Correction: If you think this is a good idea, you and anyone whose Gemfile.lock ends up including your library, intentionally or otherwise, are all gonna have a bad time.

pkurek commented 10 years ago

First of all using try is not good, cause you are asking your object about its state which is the opposite of tell don't ask rule. Additionally this feature is already there and can be easily implemented

class Foo
  include ActiveModel::AttributeMethods
  attribute_method_suffix '?'

  def attribute?(attribute) 
    send(attribute).present?
  end
end
jordaaash commented 10 years ago

I disagree that try is bad, as "tell, don't ask" is not only overly broad IMO, but doesn't apply to most metaprogramming like this, especially with method_missing which relies on respond_to? and therefore must necessarily ask the object about its "state".

Additionally, attribute_method_suffix, while elegant, is not suitable for this purpose. ActiveModel::AttributeMethods has many requirements, one of which is:

Define an attributes method which returns a hash with each attribute name in your model as hash key and the attribute value as hash value

This is incompatible with the use case this feature purports to satisfy, which is not to call methods and return a boolean as attribute? does, but to call them only if they are defined and return their value, or return nil if they are not defined (i.e., what try does).

Further, attribute? relies on Object#present?, another ActiveSupport method, as well as ActiveModel. I maintain that this entire use case and the related cases I described are satisfied (in a strictly better fashion) by try. There really is no need to reinvent that wheel, nor a need to port narrowly construed Swift features to Ruby.

pkurek commented 10 years ago

You don't have to use Object#present? it was just an example. Minimal setup is

class Foo
  include ActiveModel::AttributeMethods
  attribute_method_suffix '?'
  define_attribute_methods :name, :lastname

  attr_accessor :name, :lastname

  def initialize(opts = {})
    @name     = opts[:name]
    @lastname = opts[:lastname]
  end

  private

  def attribute?(attribute)
    send(attribute)
  end
end

I went again through your example on main page and my bad cause I misunderstood that feature. It's more like coffeescript function?()

galori commented 8 years ago

Thanks everyone for this interesting conversation.

As you all probably know, Matz announced that this feature is coming to Ruby, using the "lonely operator": &. *

So I wasnt THAT crazy, but I admit I chose a bad syntax.

The lonely operator works like this:

employee&.department&.address&.city


* According to Matz, he's calling it the lonely operator because:
> Its a guy, sitting on the floor, alone, looking at a dot. Look at him. He's lonely.