Project

General

Profile

Actions

Feature #18618

closed

no clobber def

Added by ed_ (Ed Mangimelli) 5 months ago. Updated 3 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:107806]

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!

Updated by Dan0042 (Daniel DeLorme) 5 months ago

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

Updated by rafaelfranca (Rafael França) 5 months ago

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.

Updated by rafaelfranca (Rafael França) 5 months ago

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

Updated by ed_ (Ed Mangimelli) 5 months ago

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).

Updated by ed_ (Ed Mangimelli) 5 months ago

@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

Updated by byroot (Jean Boussier) 5 months ago

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.

Updated by sawa (Tsuyoshi Sawada) 4 months ago

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.

Updated by matz (Yukihiro Matsumoto) 3 months ago

  • Status changed from Open to Closed

See #18742

Matz.

Actions

Also available in: Atom PDF