In Ruby, what do Module#instance_method, UnboundMethod#bind and bind_call do, and why use them to run an implementation a subclass overrides?
answer
- a method without a receiver
- receiver must be kind_of? the owner
- TypeError: bind argument must be an instance
- bind_call skips the Method allocation
- frozen at lookup time
basics
~20 sModule#instance_method returns an UnboundMethod, a definition without a receiver. bind(obj) attaches it to a compatible object and returns a Method; bind_call(obj, *args) binds and calls in one step. That runs one exact definition regardless of overrides.
solid answer
~40 s`ReportFormatter.instance_method(:format_row)` returns an `UnboundMethod`: the definition as found at that moment, with no receiver. `bind(obj)` returns a `Method` bound to `obj`, which must be `kind_of?` the class the method came from, or Ruby raises `TypeError` (`bind argument must be an instance of ReportFormatter`); a method owned by a module can be bound to any object. `bind_call(obj, *args)` is the same as `bind(obj).call(*args)` without allocating the intermediate `Method`. Because the definition is fixed, it runs even when `obj`'s class overrides the method or the class later redefines it, so libraries use `Kernel.instance_method(:inspect).bind_call(obj)` to reach the original behaviour of an object that overrides it.
code
ruby · 17 linesclass ReportFormatter
def format_row(row) = row.join(" | ")
end
class CsvFormatter < ReportFormatter
def format_row(row) = row.join(",")
end
base = ReportFormatter.instance_method(:format_row) # UnboundMethod
csv = CsvFormatter.new
csv.format_row(%w[a b]) # => "a,b"
base.bind_call(csv, %w[a b]) # => "a | b"
base.bind(csv).call(%w[a b]) # => "a | b", with an extra Method object
String.instance_method(:upcase).bind(42)
# TypeError: bind argument must be an instance of Stringgo deeper
Recall that instance_method gives a method with no object attached, and bind or bind_call attaches one so it can run.
Explain the kind_of? rule and its TypeError, the module exception, and that bind_call equals bind then call.
Use bind_call to reach an exact definition past overrides, know that captured definitions ignore later redefinitions, and why bind_call saves an allocation.
Weigh rebinding tricks against prepend and super for extending behaviour, favouring the mechanism that future maintainers can follow.
## A method without a receiver An **`UnboundMethod`** is a method definition that is not attached to any object. You get one in two ways: - **`Module#instance_method(:name)`** on a class or module, for example `ReportFormatter.instance_method(:format_row)`; - **`Method#unbind`** on an existing `Method`. It can report `name`, `owner`, `arity` and `parameters`, but it cannot be called until it is attached to a receiver. ## bind and bind_call | Call | Returns | Notes | |---|---|---| | `um.bind(obj)` | a `Method` bound to `obj` | reusable; call it as often as needed | | `um.bind_call(obj, *args, &blk)` | the method's result | binds and calls in one step; no `Method` object is allocated | The receiver must be compatible: 1. If the method came from a **class**, `obj.kind_of?(that_class)` must be true, otherwise `bind` raises `TypeError` with `bind argument must be an instance of ...`. 2. If the method is owned by a **module**, it can be bound to any object, because a module's methods are not tied to one class hierarchy. 3. A singleton method can only be bound to its own object. ## The definition is fixed at lookup time The core documentation states that unbound methods are a reference to the method at the time it was objectified: later changes to the class do not affect them. That has two consequences: - **Overrides are bypassed.** `ReportFormatter.instance_method(:format_row).bind_call(csv_formatter, row)` runs the base class's version even though `CsvFormatter` overrides it. - **Redefinitions are bypassed.** If `ReportFormatter` later redefines `format_row`, an `UnboundMethod` captured earlier still runs the old body. ## Where it is used - **Reaching core behaviour on objects that override it.** Debugging and serialisation code calls `Kernel.instance_method(:inspect).bind_call(obj)` or `Kernel.instance_method(:class).bind_call(obj)` so that a proxy or a class with a surprising override cannot hide the real answer. - **Dispatch tables.** The `instance_method` documentation builds a Hash of `UnboundMethod`s keyed by character and binds the right one to `self` per input. - **Wrapping an existing method.** Capture the old definition, redefine the method, and call the captured one from the new body. Modern code usually prefers `prepend` with `super` for this. ## A worked example With `ReportFormatter#format_row` joining cells with a bar and `CsvFormatter` overriding it with commas: 1. `ReportFormatter.instance_method(:format_row)` returns an `UnboundMethod` whose `owner` is `ReportFormatter`. 2. `bind_call(csv, row)` checks that `csv.kind_of?(ReportFormatter)`, which is true for a subclass instance. 3. It runs the base definition with `self` set to `csv`, so `csv`'s instance variables are visible to it, and returns the bar-joined line. 4. `csv.format_row(row)` is unaffected and still produces the comma-joined line. ## Why bind_call exists `um.bind(obj).call(args)` creates a `Method` object only to throw it away. In hot paths, such as a library that calls the original `respond_to?` or `inspect` on every object it touches, that allocation adds up. `bind_call` does the same work in one call and, when the receiver's class resolves to the same definition anyway, dispatches without building the intermediate object. ## Common mistakes - Calling `call` on an `UnboundMethod`: it has no receiver, so it must be bound first. - Binding a class's method to an unrelated object and expecting duck typing to apply; `bind` checks the class, not the methods the object has. - Expecting a captured `UnboundMethod` to pick up a later fix to the method; capture it again after the change. ## Inspecting an UnboundMethod - `um.owner` names the class or module holding the definition, which decides what `bind` accepts. - `um.name`, `um.arity` and `um.parameters` work without a receiver. - `Method#unbind` followed by `bind` to another instance of the same class moves a method between receivers.
- What happens if you capture an UnboundMethod and the class then redefines that method?The captured `UnboundMethod` keeps the definition it was created from. Binding it to a new instance and calling it runs the old body, while ordinary calls on the instance run the new one. This is what made the capture-and-redefine wrapping pattern work.
- Why can Kernel.instance_method(:inspect) be bound to an instance of any class?Its owner is the module `Kernel`, and methods owned by a module are not restricted by `kind_of?` when bound. A method taken from a class, by contrast, can only be bound to instances of that class or its subclasses.
saying these in an interview costs you the question
- An UnboundMethod can be called directly with call
- bind looks the method up again on the receiver's class
- Any UnboundMethod can be bound to any object
- bind_call skips the receiver type check to be faster
- A captured UnboundMethod picks up later redefinitions