About modifying core classes

Hello,

I started to learn ruby after reading this nasty article about PHP:
http://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/ what bothered me
more was the unpredictability and uncertainty that could arise from some
PHP settings and constructs. So I felt it might be a good time to find
another language a little more deterministic.

I just finished a Ruby tutorial on Lynda.com but there is something
that bothers me and is the fact that Ruby allows you to overwrite
the functionality of core classes, at first glance it looks like a
really good feature but... say for example that you have this code:

    integer_array = [1,3,5,7,9,18,36,64,128]

    sum = 0

    integer_array.each do |item|
      sum += item
    end

    puts sum

(I know I could have used inject for this but this is just an example).

Everything works nice and tidy I get my sum which is 271. Then I decide to
include a library that does some really useful thing into my application,
but for some reason the author of the library decided that is more
convenient for him if he could get the index of the item inside the each
block so he decides to modify the Array class like this:

    class Array
      def each
        i = 0;
        n = self.length

        while i < n do
          yield i, self[i]
          i += 1
        end
      end
    end

(forgive me if my code is not very stylish I'm a Ruby beginner)

So, anyways, the above code stills runs, but the sum I get now is 36,
no error, no warnings, nothing... I was expecting to get an argument
error (2 for 1) or something like that but nothing... and this will
probably wont pop up in unit tests because I would probably be using a
mock object instead of the actual library.

I suppose (or at least hope) that every "Ruby best practices" document says
that you SHOULDN'T do something like this but you cannot rule out the
possibility of encountering a bad programmer out there.

So, I would like to hear your thoughts on this... should this be a source
of concern? How can you be certain that you code will do what you expect it
to do if any library can change the way the core objects of the language
works and behave?

Quoting Sergio Bobillier Ceballos (bobilliersergio@gmail.com):

   So, I would like to hear your thoughts on this... should this be a source
   of concern? How can you be certain that you code will do what you expect
   it to do if any library can change the way the core objects of the
   language works and behave?

Your example is a bit contrived, because I cannot see a practical use
for your modified 'each' method. If I found it justified to modify in
my library the behaviour of a system method in such a potentially
disruptive way, I'd see it very appropriate to put out warnings in big
blinking letters. So, you would most probably find out.

Certainly pranksters have infinite ways to disrupt your code. Your
only defense is to be wary of pranksters. Do not #require
'pranklib.rb'!

Carlo

···

Subject: About modifying core classes
  Date: Tue 28 Apr 15 08:55:42PM -0500

--
  * Se la Strada e la sua Virtu' non fossero state messe da parte,
* K * Carlo E. Prelz - fluido@fluido.as che bisogno ci sarebbe
  * di parlare tanto di amore e di rettitudine? (Chuang-Tzu)

Hello,

I started to learn ruby after reading this nasty article about PHP:
PHP: a fractal of bad design / fuzzy notepad what
bothered me more was the unpredictability and uncertainty that could arise
from some PHP settings and constructs. So I felt it might be a good time
to find another language a little more deterministic.

I just finished a Ruby tutorial on Lynda.com but there is something
that bothers me and is the fact that Ruby allows you to overwrite
the functionality of core classes, at first glance it looks like a
really good feature but... say for example that you have this code:

    integer_array = [1,3,5,7,9,18,36,64,128]

    sum = 0

    integer_array.each do |item|
      sum += item
    end

    puts sum

(I know I could have used inject for this but this is just an example).

Everything works nice and tidy I get my sum which is 271. Then I decide to
include a library that does some really useful thing into my application,
but for some reason the author of the library decided that is more
convenient for him if he could get the index of the item inside the each
block so he decides to modify the Array class like this:

    class Array
      def each
        i = 0;
        n = self.length

        while i < n do
          yield i, self[i]
          i += 1
        end
      end
    end

(forgive me if my code is not very stylish I'm a Ruby beginner)

The bigger issue I have with this is that it is unlikely to happen because
you just recoded #each_with_index just with the order of block arguments
reversed. :slight_smile:

So, anyways, the above code stills runs, but the sum I get now is 36,
no error, no warnings, nothing... I was expecting to get an argument
error (2 for 1) or something like that but nothing... and this will
probably wont pop up in unit tests because I would probably be using a
mock object instead of the actual library.

I suppose (or at least hope) that every "Ruby best practices"
document says that you SHOULDN'T do something like this but you cannot rule
out the possibility of encountering a bad programmer out there.

That is correct. Monkeypatching core classes in an incompatible way is a
really DON'T. That is something everybody is expected to know and a gem
author who does it nevertheless will probably get what he deserves (no
usage of his code). :slight_smile:

So, I would like to hear your thoughts on this... should this be a source

of concern? How can you be certain that you code will do what you expect it
to do if any library can change the way the core objects of the language
works and behave?

There is no certainty - in life ever. :slight_smile: If that were a serious issue
you'd see more about this - and Matz would probably have taken counter
measures. So, don't worry, relax and enjoy Ruby programming.

Kind regards

robert

···

On Wed, Apr 29, 2015 at 3:55 AM, Sergio Bobillier Ceballos < bobilliersergio@gmail.com> wrote:

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can -
without end}
http://blog.rubybestpractices.com/

It is my understanding that Ruby is extremely dynamic in a lot of respects. For one, it's not statically typed, sou you can't rely beforehand on knowing certain variables' class, or even rely on a class' structure and methods, since everything is modifiable/extendable in runtime, even by "eval", "module_eval" etc. So e.g. you can't have static code analysis tools check your code and point out "obvious" errors like misspelled methods and such, like in Java. (Side note: in non-trivial projects that tends to give a false sense of safety anyway.)

Because of all those characteristics (including your specific concern), it's not surprising that the Ruby community seems to tend towards and promote test driven development. Cover your syntax and logic (basic and application-level) with unit/integration tests - so whenever any assumptions you make about Ruby, the libraries you use, or your own code proves false, your tests will most probably pinpoint them (assuming you have well thought-out test cases). Obviously you won't include test cases to cover such core system functionality directly, but changes the likes you mentioned will certainly break your own code anyway (that's the point) in a testable way.

This approach has the added benefit of giving you freedom to upgrade Ruby, frameworks and gems, and also refactor your own code in a much more relaxed way, since you can feel covered that if you break something, you'll know it right away by your tests failing.

regards
mortee

···

On 2015.04.29. 03:55, Sergio Bobillier Ceballos wrote:

Hello,

I started to learn ruby after reading this nasty article about PHP: PHP: a fractal of bad design / fuzzy notepad what bothered me more was the unpredictability and uncertainty that could arise from some PHP settings and constructs. So I felt it might be a good time to find another language a little more deterministic.

I just finished a Ruby tutorial on Lynda.com but there is something that bothers me and is the fact that Ruby allows you to overwrite the functionality of core classes, at first glance it looks like a really good feature but... say for example that you have this code:

    integer_array = [1,3,5,7,9,18,36,64,128]

    sum = 0

    integer_array.each do |item|
      sum += item
    end

    puts sum

(I know I could have used inject for this but this is just an example).

Everything works nice and tidy I get my sum which is 271. Then I decide to include a library that does some really useful thing into my application, but for some reason the author of the library decided that is more convenient for him if he could get the index of the item inside the each block so he decides to modify the Array class like this:

    class Array
      def each
        i = 0;
        n = self.length

        while i < n do
          yield i, self[i]
          i += 1
        end
      end
    end

(forgive me if my code is not very stylish I'm a Ruby beginner)

So, anyways, the above code stills runs, but the sum I get now is 36, no error, no warnings, nothing... I was expecting to get an argument error (2 for 1) or something like that but nothing... and this will probably wont pop up in unit tests because I would probably be using a mock object instead of the actual library.

I suppose (or at least hope) that every "Ruby best practices" document says that you SHOULDN'T do something like this but you cannot rule out the possibility of encountering a bad programmer out there.

So, I would like to hear your thoughts on this... should this be a source of concern? How can you be certain that you code will do what you expect it to do if any library can change the way the core objects of the language works and behave?

should this be a source of concern? How can you be certain that you code

will do what you expect it to do if any library can change the way the core
objects of the language works and behave?

Quick-and-dirty code modification techniques like this can help scripts or
end-of-the-line apps doing a particular job be written shorter and faster,
but aren't "best practice" Ruby for code you expect to last or be reused
widely.

Monkey patches that break the way the core is expected to work are not
everyday programming and good program design will avoid it: "open for
extension but closed for modification" is enforced by convention rather
than by compiler law.

Whenever you're including other code, in any language, there are more
concerns than this. In a language where you can't redefine String, you
still have to trust that 'include HotNewLibrary' isn't going to 'rm -rf /'
or upload all the email addresses on the system to an IRC network.

Happy programming!

Cheers,
Dave

There's a reason some companies have harsh rules on including and vetting
gems. It's pretty well asking for trouble to include a new gem for every
single thing you could possibly use.

Likewise, the same level of caution should be exercised in monkeypatching
core classes. There are most certainly uses for it, but in most cases
either a decorator or other function could be used. A monkeypatch should
only be considered in the case of an extremely common pattern, such as a
few I've used myself:

module Enumerable
  def count_by(&block)
    group_by(&block).map { |k, v| [k, v.length] }.to_h
  end
end

(1..10).count_by(&:even?)
# => => {false=>5, true=>5}

class Object
  def pipe
    yield self
  end
end

People.where(age: 35).pipe { |q|
  params[:something] ? q.where(x: 5) : q.where(y: 10)
}

Granted that pipe is a bit more contrived and will probably have a general
implementation whenever the core team adds a block option to itself

Hi Sergio,

You're right to be worried about it.
With great powers comes great responsabilities!!! :wink:

Have a look at "Refinements", a new Ruby language feature.

http://ruby-doc.org/core-2.2.2/doc/syntax/refinements_rdoc.html

Best regards,
Abinoam Jr.

···

On Wed, Apr 29, 2015 at 2:39 AM, Brandon Weaver <keystonelemur@gmail.com> wrote:

There's a reason some companies have harsh rules on including and vetting
gems. It's pretty well asking for trouble to include a new gem for every
single thing you could possibly use.

Likewise, the same level of caution should be exercised in monkeypatching
core classes. There are most certainly uses for it, but in most cases either
a decorator or other function could be used. A monkeypatch should only be
considered in the case of an extremely common pattern, such as a few I've
used myself:

module Enumerable
  def count_by(&block)
    group_by(&block).map { |k, v| [k, v.length] }.to_h
  end
end

(1..10).count_by(&:even?)
# => => {false=>5, true=>5}

class Object
  def pipe
    yield self
  end
end

People.where(age: 35).pipe { |q|
  params[:something] ? q.where(x: 5) : q.where(y: 10)
}

Granted that pipe is a bit more contrived and will probably have a general
implementation whenever the core team adds a block option to itself

And not just in Ruby, or even just dynamic languages.

Anyone for a bit of C pranking?

    #undef NULL
    #define NULL (-1)

Generally: all code has a contract (defined inputs, outputs, and
side-effects). If you don't trust a piece of code to abide by its
contract, don't use that code.

···

On 29/04/2015, Carlo E. Prelz <fluido@fluido.as> wrote:

Certainly pranksters have infinite ways to disrupt your code. Your
only defense is to be wary of pranksters. Do not #require
'pranklib.rb'!

--
  Matthew Kerwin
  http://matthew.kerwin.net.au/