Why a Module's Singleton Method Is Not Visible in Downstream Eigenclasses Where It Gets Mixed

Why a module's singleton method is not visible in downstream eigenclasses where it gets mixed?

First of all, include does not include eigenclass methods as you might expect. Consider:

module Foo
class << self
def do_something
puts "Foo's eigenclass method does something"
end
end
end

module Bar
include Foo
end

puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)

Note that this is consistent with the behavior of classically defined class methods:

module Foo
def self.do_something
puts "Foo's class method does something"
end
end

module Bar
include Foo
end

puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)

A common idiom is to define the class methods in a submodule and then trigger a call to extend when the module is included:

module Foo
def self.included(base)
base.extend ClassMethods
end

module ClassMethods
def do_something
puts "Foo::ClassMethod's instance method does something"
end
end
end

module Bar
include Foo
end

puts Bar.do_something
# Foo::ClassMethod's instance method does something

The second thing to note is, that you are really including the instance methods of Automobile into the eigenclass of Vehicle, thus the instance methods of Automobile turn into (eigen)class methods of Vehicle.

Your Car class basically has nothing to do with all this. The only thing to note here is, that class inheritance also makes class methods available, whereas include does not. Example:

class Foo
def self.do_something
puts "Foo's class method does something"
end
end

class Bar < Foo
end

puts Bar.do_something
# "Foo's class method does something"

Class, Module, their eigenclasses, and method lookup

I recently wrote a pretty extensive tutorial, including new Ruby 2.0 behavior.

Note: the term used in Ruby is singleton_class, not eigenclass.

Why am I able to use Kernel singleton methods like `puts`?

You are looking in the wrong place.

Methods like Kernel#Array, Kernel#Complex, Kernel#Float, Kernel#Hash, Kernel#Integer, Kernel#Rational, Kernel#String, Kernel#__callee__, Kernel#__dir__, Kernel#__method__, Kernel#`, Kernel#abort, Kernel#at_exit, Kernel#autoload, Kernel#autoload?, Kernel#binding, Kernel#block_given?, Kernel#callcc, Kernel#caller, Kernel#caller_locations, Kernel#catch, Kernel#eval, Kernel#exec, Kernel#exit, Kernel#exit!, Kernel#fail, Kernel#fork, Kernel#format, Kernel#gets, Kernel#global_variables, Kernel#initialize_clone, Kernel#initialize_copy, Kernel#initialize_dup, Kernel#iterator?, Kernel#lambda, Kernel#load, Kernel#local_variables, Kernel#loop, Kernel#open, Kernel#p, Kernel#pp, Kernel#print, Kernel#printf, Kernel#proc, Kernel#putc, Kernel#puts, Kernel#raise, Kernel#rand, Kernel#readline, Kernel#readlines, Kernel#require, Kernel#require_relative, Kernel#select, Kernel#set_trace_func, Kernel#sleep, Kernel#spawn, Kernel#sprintf, Kernel#srand, Kernel#syscall, Kernel#system, Kernel#test, Kernel#throw, Kernel#trace_var, Kernel#trap, Kernel#untrace_var, and Kernel#warn don't do anything useful with their receiver. They don't call private methods, they don't access instance variables, they in fact completely ignore what self is.

Therefore, it would be misleading if you call them like this:

foo.puts 'Hello, World!'

Because a reader would be mislead into thinking that puts does something with foo, when in fact, it completely ignores it. (This applies especially to the printing family of methods, because there also exist IO#puts and friends, which indeed do care about their receiver.)

So, in order to prevent you from misleadingly calling these methods with a receiver, they are made private, which means they can only be called without an explicit receiver. (Obviously, they will still be called on self, but at least that won't be so obvious visually.)

Technically, these aren't really methods at all, they behave more like procedures, but Ruby doesn't have procedures, so this is the best way to "fake" them.

The reason why they are also defined as singleton methods is so that you can still call them in contexts where Kernel is not in the inheritance hierarchy, e.g. something like this:

class Foo < BasicObject
def works
::Kernel.puts 'Hello, World!'
end

def doesnt
puts 'Hello, World!'
end
end

f = Foo.new

f.works
# Hello, World!

f.doesnt
# NoMethodError: undefined method `puts' for #<Foo:0x00007f97cf918ed0>

And the reason why they need to be defined separately at all is that the instance method versions are private. If they weren't, then you would simply be able to call Kernel.puts anyway, because Object includes Kernel and Kernel is an instance of Module which is a subclass of Object, thus Kernel is an indirect instance of itself. However, the methods are private and thus you would get a

NoMethodError: private method `puts' called for Kernel:Module

instead. Therefore, they need to be duplicated separately. There is actually a helper method that does that: Module#module_function. (This is also used for Math, where you can either call e.g. Math.sqrt(4) or include Math; sqrt(4). In this case, you have the choice of includeing Math or not, whereas Kernel is pre-included in Object always.)

So, in summary: the methods are duplicated as private instance methods of Kernel as well as public singleton methods (which is really just instance methods of Kernel's singleton class). The reason they are defined as private instance methods is so they cannot be called with an explicit receiver and are forced to look more like procedures. The reason they are duplicated as singleton methods of Kernel is so that they can be called with an explicit receiver as long as that explicit receiver is Kernel, in contexts where Kernel is not available in the inheritance hierarchy.

Check this out:

#ruby --disable-gems --disable-did_you_mean -e'puts Kernel.private_instance_methods(false).sort'
Array
Complex
Float
Hash
Integer
Rational
String
__callee__
__dir__
__method__
`
abort
at_exit
autoload
autoload?
binding
block_given?
caller
caller_locations
catch
eval
exec
exit
exit!
fail
fork
format
gets
global_variables
initialize_clone
initialize_copy
initialize_dup
iterator?
lambda
load
local_variables
loop
open
p
pp
print
printf
proc
putc
puts
raise
rand
readline
readlines
require
require_relative
respond_to_missing?
select
set_trace_func
sleep
spawn
sprintf
srand
syscall
system
test
throw
trace_var
trap
untrace_var
warn

Ruby module include, can't access included methods, only constants

The following is a common way to mix a module containing constants, instance methods, and class methods into a class, but it can also be used to include constants and class methods of one module in another module, which is what you want to do. It uses the "callback" or "hook" method Module#included. Object#extend adds the instance methods in the module that is the argument to the module that is extend's receiver. Below it makes the instance methods (here just one) in Public::C_Meths class methods in the module Child.

module Parent
module I_Meths
PARENT_CONST = 'parent const'
end

module C_Meths
def cmeth
'cmeth'
end
end

def self.included(mod)
mod.include(I_Meths)
mod.extend(C_Meths)
end
end

module Child
include Parent
end

Child::PARENT_CONST
#=> "parent const"
Child.cmeth
#=> "cmeth"

It's more common to use this construct to mix a module containing constraints, instance methods and class methods into a class.

Suppose we were to add the instance method:

def imeth
'imeth'
end

to the module Parent::I_Meths and include Parent in a class:

class A
include Parent
end

then

A::PARENT_CONST
#=> "parent const"
A.cmeth
#=> "cmeth"
A.new.imeth
#=> "imeth"


Related Topics



Leave a reply



Submit