Feature #18618
closed
Added by ed_ (Ed Mangimelli) over 2 years ago.
Updated over 2 years ago.
Description
Sometimes I want to be certain I'm not clobbering/masking a method:
class Dog
def bark
'bark!'
end
end
class Poodle < Dog
raise if method_defined? :bark
def bark
'bow-wow'
end
end
I propose creating a shorthand. Maybe something like:
class Dog
def bark
'bark!'
end
end
class Poodle < Dog
ncdef bark # "no clobber" def
'bow-wow'
end
end
=> #<MethodAlreadyDefined: Method `bark' already defined.>
This would be useful in scenarios where subclassing a class (or including a mixin) you don't own is common practice --for instance, subclassing ApplicationRecord
for your model in Rails.
I agree that ncdef
is pretty ugly. Maybe def!
I like this idea.
But no one will use this feature if you need to always opt-in via ncdef
.
It would make more sense as a $VERBOSE warning that can be turned off when you know you want to clobber.
class Dog
def a; end
def b; end
def b; end #=> already has a warning; avoid by using `undef b`
end
class Poodle < Dog
no_clobber! #maybe turn on warnings for this class via DSL?
def a; super; end #=> no warning due to `super` keyword in method
def b; end #=> warning; avoid by using `undef b`
end
Isn’t this the same idea as final methods in other languages like Java?
There is an implementation for this https://github.com/joker1007/finalist.
Support for final methods would allow libraries and application developers to clearly communicate what methods of a class can be overridden and which ones should not. Active Record objects are a good example. If someone defines a method called ‘create_record’ they will be in trouble and the library could mark that method as final to avoid that mistake.
Override can be paired with final to allow the users to explicitly tell they are aware they are overriding a final method. Although, in that case the name ‘final’ doesn’t make much sense.
Ah, forgot to say sorbet already have support to final, abstract and override. Having support in Ruby could help with static analysis in sorbet and steep.
https://sorbet.org/docs/final
Isn’t this the same idea as final methods in other languages like Java?
Had this discussion with a coworker --final
is similar but is for the opposite direction. final
lets the library author prevent overriding; I'm wanting a tool as the library consumer to avoid accidental overriding.
A library author using final
definitely does solve my problem, but then the onus is on the author (and I'm much more often the library consumer).
@Dan0042 (Daniel DeLorme) I like that syntax --much more convenient-- but I don't know if it would be useful to me if it were just toggling the existing warning.
But that syntax seems great:
class MyClass < LibraryProvidedClass
no_clobber
def a; end
def b; super; end # no effect (`super` being called)
def c; end
override def d; end # maybe an override keyword to use in conjunction with `no_clobber`?
end
=> #<MethodAlreadyDefined: Method `c' already defined.>
class MyClass < LibraryProvidedClass
def a; end
no_clobber def b; end # can also be used in a one-off way like `private`
def c; end
end
Note that you could perfectly implement this in pure Ruby today.
module ClobberChecker
def method_added(name)
super
no_clobber(name, caller(1, 1).first) if @no_clobber
end
end
class Class
def no_clobber(method_name = nil, callsite = caller(1, 1).first)
if method_name
if (super_method = instance_method(method_name).super_method)
warn "Clobbering #{super_method.owner}##{super_method.name} (#{callsite})"
end
else
extend(ClobberChecker)
@no_clobber = true
end
end
end
class A
def foo
end
def bar
end
end
class B < A
no_clobber def foo
end
no_clobber
def bar
end
end
There might be some subtleties though, if for instance you include a module after defining a method.
The fact that you want to raise an error suggests that you want this feature to take place during development, and not during production: You want to be notified when writing code so that you do not accidentally overwrite a method. If a new method overwrites an old one, then you want to remove the new definition entirely from the code, and if it does not, then you just want to end up with an ordinary def
block in the final production code.
If that is the case, I think such feature would be the responsibility of an IDE, and not of the Ruby implementation.
Or, if that is not the case, and you want to keep the definition in the code base regardless of whether it overwrites or not, but not let the overwriting take effect, then in such case, I think you need some different mechanism, and your proposal here would be in the wrong direction.
- Status changed from Open to Closed
Also available in: Atom
PDF
Like0
Like0Like0Like0Like0Like0Like0Like0Like0