Skip to content

Instantly share code, notes, and snippets.

@deepak
Last active December 31, 2015 06:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deepak/f5dd39ed1aec93bbd538 to your computer and use it in GitHub Desktop.
Save deepak/f5dd39ed1aec93bbd538 to your computer and use it in GitHub Desktop.
why Fixnum#respond_to?(:dup) is not false

https://twitter.com/lkdjiin/status/682209935237582848

1.respond_to?(:dup) #=> true
1.dup #=> TypeError

Have to manually :

class Fixnum
  undef_method :dup
end

Any reasons?

TL;DR I think ruby does what it does wrt Fixnum#dup for human-readable error message
Ideally, would like Fixnum#respond_to? to return false but keep the error message the same

I think defining Object#dup is reasonable

o = Object.new
o.object_id == o.dup.object_id #=> false 

What it tells us is that - all Objects are potentially dup-able
so not a bad design decision IMO

Individial classes and objects can always:

  1. override dup and return TypeError
  2. undefined dup method

Not sure which method is right or i perfer!

But i think, throwing a TypeError: can't dup Fixnum error is more human friendly than throwing a NoMethodError

Before this tweet never found it to be a problem.
But now, personally my ideal situation will be:

  1. there is a Dupable module like Comparable with a dup method. and Fixnum does not include it
  2. so 1.respond_to?(:dup) to return false
  3. but calling 1.dup to throw NoMethodError: can't dup Fixnum or TypeError: can't dup Fixnum ie. throw an error with a human friendly error message

The problem with (1) is that there might be "too-many" modules :-)

can meet some of the requirements by monkey-patching it as:

class Fixnum
   def respond_to?(msg_id, priv=false)
      return false if msg_id.to_sym == :dup
      super
    end
end

1.class #=> Fixnum
1.respond_to? :dup #=> false
1.dup #~> TypeError: can't dup Fixnum

but atleast ruby is consistent:

  1. same error is there for symbols as well eg. :foo.dup
  2. same error for Singletons

Do not know the exact technical detail, of how it is implemented
but as a mental rule of thumb,
I think of Symbols, Integers and Singletons as being interned - of sorts

I think of them being immutable and allocated once on the heap like Java's Strings

and so calling dup on them does not make sense. hence the error

for symbols:

:foo.object_id == :foo.object_id #=> true
:foo.dup #~> TypeError: can't dup Symbol

it is consistent with Singleton as well:

require 'singleton'

class SingleKlass
  include Singleton
end

SingleKlass.instance.object_id == SingleKlass.instance.object_id #=> true
SingleKlass.instance.dup #~> TypeError: can't dup instance of singleton SingleKlass

btw can dup BigDecimal

require 'bigdecimal'

BigDecimal.new("1").object_id == BigDecimal.new("1").object_id #=> false

o = BigDecimal.new("1")
o.object_id == o.dup.object_id #=> false

And all methods of Object are implemented in Fixnum, Symbol and Singleton

(Object.new.methods.sort - 1.methods.sort).empty? #=> true
(Object.new.methods.sort - :foo.methods.sort).empty? #=> true
(Object.new.methods.sort - SingleKlass.instance.methods.sort).empty? #=> true

I think, the decision for Fixnum#dup to raise an error is similar to FrozenObject#taint returning an error

o = Object.new
o.freeze #=> true
o.frozen? #=> true
o.taint #~> RuntimeError: can't modify frozen Object

1.frozen? #=> true
1.taint #=> RuntimeError: can't modify frozen Fixnum

If a frozen object undefines taint then, we will have to rescue NoMethodError. but that is a very general error
Assuming that rescuing NoMethodError is bad, we will have to check with respond_to? before calling any method
I think both are sub-optimal.
But this is not a strong argument as both RuntimeError and TypeError feel like general errors

So i think ruby does what it does wrt Fixnum#dup for human-readable error message.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment