setting local variables in a binding

M

Martin DeMello

Why does this not work?

ruby-1.9.2-p0 > b = binding
=> #<Binding:0x9adc81c>
ruby-1.9.2-p0 > eval("x = 5", b)
=> 5
ruby-1.9.2-p0 > x
NameError: undefined local variable or method `x' for main:Object
from (irb):6
from /home/martin/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
ruby-1.9.2-p0 > eval("x")
NameError: undefined local variable or method `x' for main:Object
from (irb):7:in `eval'
from (irb):7:in `eval'
from (irb):7
from /home/martin/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
ruby-1.9.2-p0 > eval("x", b)
=> 5

martin
 
B

Brian Candler

Martin said:
Why does this not work?

ruby-1.9.2-p0 > b = binding
=> #<Binding:0x9adc81c>
ruby-1.9.2-p0 > eval("x = 5", b)
=> 5
ruby-1.9.2-p0 > x
NameError: undefined local variable or method `x' for main:Object
from (irb):6
from /home/martin/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'

irb(main):001:0> b = binding
=> #<Binding:0x7f9079ec7460>
irb(main):002:0> eval("x = 5", b)
=> 5
irb(main):003:0> x
=> 5
irb(main):004:0> RUBY_DESCRIPTION
=> "ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]"

It might be an irb-ism in 1.9. Have you tried it in a standalone .rb
program?
 
M

Martin DeMello

It might be an irb-ism in 1.9. Have you tried it in a standalone .rb
program?

Yes, didn't work there either. Didn't think to try 1.8 - wonder why
the behaviour changed.

martin
 
J

John Sikora

Martin said:
Why does this not work?

ruby-1.9.2-p0 > b = binding
=> #<Binding:0x9adc81c>
ruby-1.9.2-p0 > eval("x = 5", b)
=> 5
ruby-1.9.2-p0 > x
NameError: undefined local variable or method `x' for main:Object
from (irb):6
from /home/martin/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
ruby-1.9.2-p0 > eval("x")
NameError: undefined local variable or method `x' for main:Object
from (irb):7:in `eval'
from (irb):7:in `eval'
from (irb):7
from /home/martin/.rvm/rubies/ruby-1.9.2-p0/bin/irb:17:in `<main>'
ruby-1.9.2-p0 > eval("x", b)
=> 5

martin

Hmmm, it seems to work when x is already defined (in 1.9.2):

irb(main):001:0> x=4
=> 4
irb(main):002:0> b=binding
=> #<Binding:0xfa2ab0>
irb(main):003:0> eval("x=5", b)
=> 5
irb(main):004:0> x
=> 5
irb(main):005:0>

If I do not set x to a value beforehand, I get the same error. Do not
know why though.

js
 
M

Martin DeMello

Hmmm, it seems to work when x is already defined (in 1.9.2):

irb(main):001:0> x=4
=> 4
irb(main):002:0> b=binding
=> #<Binding:0xfa2ab0>
irb(main):003:0> eval("x=5", b)
=> 5
irb(main):004:0> x
=> 5
irb(main):005:0>

If I do not set x to a value beforehand, I get the same error. Do not
know why though.

Oddly enough, if x is defined beforehand you don't even need the binding:

ruby-1.9.2-p0 > x = 5
=> 5
ruby-1.9.2-p0 > eval "x = 42"
=> 42
ruby-1.9.2-p0 > x
=> 42

martin
 
T

Tony Arcieri

[Note: parts of this message were removed to make it a legal post.]

Note that "binding" is the default argument of eval if you don't specify
one, so capturing binding as a variable and passing it explicitly is
redundant.
 
J

John Sikora

Tony said:
Note that "binding" is the default argument of eval if you don't specify
one, so capturing binding as a variable and passing it explicitly is
redundant.

You are correct but the problem 'persists'. The following irb session in
1.9.2 shows the same thing without the binding:

irb(main):001:0> eval("x=5")
=> 5
irb(main):002:0> x
NameError: undefined local variable or method `x' for main:Object
from (irb):2
from C:/Ruby192/bin/irb:12:in `<main>'
irb(main):003:0> x=4
=> 4
irb(main):004:0> eval("x=5")
=> 5
irb(main):005:0> x
=> 5
irb(main):006:0>


In order for this to work, the x needs to be @x per the following irb
session (again 1.9.2):

irb(main):001:0> eval("@x=5")
=> 5
irb(main):002:0> @x
=> 5
irb(main):003:0>

The behavior seen in the first irb session was the same in 1.8.6, 1.9.1,
and 1.9.2 when run standalone. However, it works in irb for 1.8.6 (x w/o
the @).

I have sometimes seen 'local' or 'unassociated' instance variables (not
sure if my terminology is correct) in example programs and I have
wondered why they are used. I guess this is one reason although most
people don't like to use eval.

I use eval in personal scripts since it is so convenient. Had to go back
and look at some code to remind myself how I got around the behavior
seen in the first irb session. I don't remember if I read about using
the local instance variable or just played around with it until I got it
to work.

js
 
R

Robert Klemme

You are correct but the problem 'persists'. The following irb session in
1.9.2 shows the same thing without the binding:

irb(main):001:0> eval("x=3D5")
=3D> 5
irb(main):002:0> x
NameError: undefined local variable or method `x' for main:Object
=A0 =A0 =A0 =A0from (irb):2
=A0 =A0 =A0 =A0from C:/Ruby192/bin/irb:12:in `<main>'
irb(main):003:0> x=3D4
=3D> 4
irb(main):004:0> eval("x=3D5")
=3D> 5
irb(main):005:0> x
=3D> 5
irb(main):006:0>

The reason is that Ruby looks at the source code to find out which are
local variables. This is basically the same as doing

13:00:57 ~$ ruby19 -e 'def f;puts "x=3D10";p x end;f'
x=3D10
-e:1:in `f': undefined local variable or method `x' for main:Object (NameEr=
ror)
from -e:1:in `<main>'
13:00:59 ~$ ruby19 -e 'def f;eval "x=3D10";p x end;f'
-e:1:in `f': undefined local variable or method `x' for main:Object (NameEr=
ror)
from -e:1:in `<main>'
13:01:02 ~$

In other words: the interpreter does not know that there is an
assignment to x in the eval code so x is undefined.
In order for this to work, the x needs to be @x per the following irb
session (again 1.9.2):

irb(main):001:0> eval("@x=3D5")
=3D> 5
irb(main):002:0> @x
=3D> 5
irb(main):003:0>

That's an instance variable which is a completely different beast.
The behavior seen in the first irb session was the same in 1.8.6, 1.9.1,
and 1.9.2 when run standalone. However, it works in irb for 1.8.6 (x w/o
the @).

irb has some specialities with handling local variables which is the
reason it behaves differently. The reason is that it will not parse
the code in one go because it only ever sees a chunk (every time you
hit enter). If the interpreter's regular behavior would be retained
you could not use local variables in irb which would be inconvenient.
I have sometimes seen 'local' or 'unassociated' instance variables (not
sure if my terminology is correct)

Not sure what you actually mean by that.
in example programs and I have
wondered why they are used. I guess this is one reason although most
people don't like to use eval.

The reasons against eval are more in the area of safety and
performance. If you know which variable you want to assign you can
use it directly. If you do not know you typically use a Hash.
I use eval in personal scripts since it is so convenient. Had to go back
and look at some code to remind myself how I got around the behavior
seen in the first irb session. I don't remember if I read about using
the local instance variable or just played around with it until I got it
to work.

What do you use eval for? In cases where the set of variables is not
fixed at coding time chances are that you usually want a Hash. Using
eval should be really be rare and for specific cases but not an
everyday habit IMHO.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
J

John Sikora

Robert said:
Not sure what you actually mean by that.

I mean that I have seen examples where people use, say '@var', outside
of any class, module, or method; in the 'main body' of the program. I
would think that they would just use 'var' instead. I do not see why it
would be advantageous to use '@var'. Is there a reason to do this?
What do you use eval for? In cases where the set of variables is not
fixed at coding time chances are that you usually want a Hash. Using
eval should be really be rare and for specific cases but not an
everyday habit IMHO.

I mainly use 'eval' to evaluate syntactic sugar expressions for
enumerable methods such as 'sort', 'find_all', 'all?', etc. I have
created a Module with enumerable classes (they have an 'each' method)
and I have added syntactic sugar to some of the methods. So I can be
lazy (and quick) and write things like:

Slot.enum.find_all(‘:slot_num >= 8’, ‘:slot_num <= 22’, “:rate ==
‘ufec’”).all?(‘:errors == 0’)

As you can probably guess, anything with a ':' before it is an attribute
that gets evaluated. Then the expression gets evaluated. I know I could
use ‘send’ for the first evaluation, but sometimes I chain together
attributes of attributes, use variables in the expressions, etc., and it
gets more complicated. 'eval' is my easy way out (I’m just a hardware
engineer trying to make my life easier). In case anyone is wondering,
the parameters given in 'find_all' have the option of an ‘and’ or ‘or’
function. Default is ‘and’ as in the example.

Speed is generally not an issue and I am somewhat aware of the dangers
of using 'eval'. I try to isolate the 'eval' expressions by limiting the
user (me, mostly) to commands that have pre-programmmed options. A
malicious user could probably find a way around it though since I am not
too savvy in this area.

I am always looking to improve the code and if anyone can suggest ways
of eliminating the 'eval' statements while keeping the same
functionality, I will certainly listen. I am also trying to read up on
the latest dynamic techniques that rely less upon 'eval'.

js
 
R

Robert Klemme

I mean that I have seen examples where people use, say '@var', outside
of any class, module, or method; in the 'main body' of the program. I
would think that they would just use 'var' instead. I do not see why it
would be advantageous to use '@var'. Is there a reason to do this?


I mainly use 'eval' to evaluate syntactic sugar expressions for
enumerable methods such as 'sort', 'find_all', 'all?', etc. I have
created a Module with enumerable classes (they have an 'each' method)

So you have something like

class Foo
def self.each
yield "whatever"
self
end
end

? Did you also do

class Foo
def self.each
yield "whatever"
end

extend Enumerable
end

? That would give you all methods like #find_all etc. for free.
and I have added syntactic sugar to some of the methods. So I can be
lazy (and quick) and write things like:

Slot.enum.find_all(=91:slot_num >=3D 8=92, =91:slot_num <=3D 22=92, =93:r= ate =3D=3D
=91ufec=92=94).all?(=91:errors =3D=3D 0=92)

Is this equivalent to

enum.find_all {|s| (8..22) =3D=3D=3D s.slot_num && s.rate =3D=3D 'ufec'}.al=
l?
{|s| e.errors =3D=3D 0}

? If yes, I do not really see the advantage of your approach. It's
slower needs a similar amount of typing and will detect errors later
(at execution time vs. at parse time).
As you can probably guess, anything with a ':' before it is an attribute
that gets evaluated. Then the expression gets evaluated. I know I could
use =91send=92 for the first evaluation, but sometimes I chain together
attributes of attributes, use variables in the expressions, etc., and it
gets more complicated. 'eval' is my easy way out (I=92m just a hardware
engineer trying to make my life easier). In case anyone is wondering,
the parameters given in 'find_all' have the option of an =91and=92 or =91= or=92
function. Default is =91and=92 as in the example.

If my assumption above is correct I do not understand where the "easy
way out" is.
Speed is generally not an issue and I am somewhat aware of the dangers
of using 'eval'. I try to isolate the 'eval' expressions by limiting the
user (me, mostly) to commands that have pre-programmmed options. A
malicious user could probably find a way around it though since I am not
too savvy in this area.

I am always looking to improve the code and if anyone can suggest ways
of eliminating the 'eval' statements while keeping the same
functionality, I will certainly listen. I am also trying to read up on
the latest dynamic techniques that rely less upon 'eval'.

(see above)

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
R

Robert Klemme

Robert,
Note that I have kept the code and comments to a minimum here as I did
not want the post to be even longer. I know that you are quite
knowledgeable and that you should have little trouble following what I
am doing - although you might not necessarily understand why I chose to
do it this way:

Fluent speaking of Ruby makes it somewhat easier but even for me the
length of the post needs some digesting. So I won't come up with a full
coverage of everything you wrote for now. I hope my remarks are useful
nevertheless.
class Base
@class_all_enum_objects = Array.new

class<< self
attr_accessor :class_all_enum_objects
end

def initialize
self.class.class_all_enum_objects<< self
end

def self.enum
EnumeratorEOC.new(self, :each)
end

def self.each(&code_block)
self.class_all_enum_objects.each do |object|
yield object
end
end
end

module EnumeratorModifierEOC

This should rather be a EnumerableModifierEOC IMHO since #find is
defined in Enumerable and this module contains all the enumerating
methods which are based on #each (e.g. find_all, inject, map).
def find_all(*condition_strings,&code_block)
if block_given?
find_array = super
else
find_array = # process condition strings (syntactic sugar) here.
end
ArrayEOC.new(find_array)
end

# code for other methods such as sort, min, max, all?, etc. here.
end

class EnumeratorEOC< Enumerator # avoids modifying Enumerator.
include EnumeratorModifierEOC

def initilize(object, method_sym)
super
end
end

class ArrayEOC< Array # avoids modifying Array.
include EnumeratorModifierEOC

def initialize(array = [])
super(array)
end
end

Although no additional methods are shown (these are presented below if I
am not mistaken) the purpose of EnumeratorEOC and ArrayEOC is mainly to
augment standard types with additional functionality it seems. Correct?
To suit my purposes: 1) I want the class itself to keep track of all of
the class instances, as shown above, 2) if an attribute has more than
one value or is more complex than an Array, I prefer to define a class
for it (inherited from Base) and assign attributes, 3) I want syntactic
sugar for enumerables, 4) especially for sort, including default sort
parameters (min and max get defaults for free as a result of the
necessary<=> definition). 5) I want to be able to define new functions
and to be able to use syntactic sugar with them. See below. 6) I also
keep track of descendants of classes although this is only partly shown
below. There is a little more on this at the end if anyone is still
reading.


Yes, you are correct (e.errors is a typo - should be s.errors), they are
equivalent.

Sorry, that was a typo. Thanks for catching that.
I think 'sort' is the main reason for my 'easy way out'. Plus the use of
additional functions. To illustrate, I have included in the code below
one such function, 'sum_total_eoc' which is just 'inject' set up for
addition, but with the ability to use syntactic sugar (of course).

In the code above I do not see any usage of #sort so I don't really see
the easy way out in the example above. Even if you would need the
String argument eval solution for sorting it would not be necessary for
the code above which uses #find_all and #all?.
You may or may not agree with my 'easy way out', but here goes:

First, a short explanation: I have attribute and expression comparables
for each class. The comparables are compared in the order that they are
given in attr_accessor. There is also a class method called
set_comparables_order that allows the user to re-arrange the order if
desired (it also allows expressions, not just attributes). Child classes
inherit the comparables and their order from the parent class. Any new
attributes defined in attr_accessor in the child class are added to the
comparables up front.

A problem of your design is that you change the classes idea of default
ordering. What I mean is this: you need to modify a class to achieve a
particular ordering. Now the standard behavior of sorting has changed
and you can never tell what ordering you will get by only looking at a
particular piece of code which only contains the call to #sort.

IMHO it would be better to not allow this but instead create a mechanism
which makes the ordering explicit. Here is one approach

module EnumeratorModifierEOC # or Enumerable
def sort_by_fields(*fields)
sort_by {|x| fields.map {|f| x.send(f)}}
end
end

This might not be the most efficient solution because of the repeated
invocation of #map. Alternatively you can easily stick with #sort_by by
doing

enum.sort_by {|x| [x.field_1, x.field_2]}

which is pretty short, readable and efficient.

You could improve this by creating something like a Sorter:

def Sorter(*fields)
eval "lambda {|enum| enum.sort_by{|x| [#{fields.map {|f|
"x.#{f}"}.join(', ')}]}}"
end

MAIN = Sorter:)foo, :bar)
...
sorted = MAIN[enum]

Now you actually have your eval but you use it to compile code which you
can then use more often efficiently. :)

You can even make this more modular by removing the sorting and
compiling only the field extraction:

def FieldExtractor(*field)
eval "lambda {|x| [#{fields.map {|f| "x.#{f}"}.join(', ')}]}"
end

MAIN = FieldExtractor:)foo, :bar)
...
sorted = enum.sort_by &MAIN
OK, so what can I do with this? The following are some simplified
examples:

class Slot< EnumerableObjectClass::Base
attr_accessor :slot_num, :rate, :error_info #<--Array of ErrorInfo
objects.

def initialize(slot_num, rate, error_info)
super()
@slot_num, @rate, @error_info = slot_num, rate, error_info
end
end

class ErrorInfo< EnumerableObjectClass::Base
attr_accessor :time_stamp, :num_errors

def initialize(time_stamp, num_errors)
super()
@time_stamp, @num_errors = time_stamp, num_errors
end
end

Slot.new(3, 'efec', [ErrorInfo.new('10-07-10', 500),
ErrorInfo.new('10-08-10', 1000)])
Slot.new(1, 'ufec', [ErrorInfo.new('10-04-10', 1000),
ErrorInfo.new('10-05-10', 2000)])
Slot.new(8, 'ufec', [ErrorInfo.new('10-07-10', 1500),
ErrorInfo.new('10-08-10', 1200)])
Slot.new(4, 'efec', [ErrorInfo.new('10-07-10', 3500)])
Slot.enum{}.find(':slot_num == 4').error_info<<
ErrorInfo.new('10-08-10', 2500)

-------------
# note - most of the examples below do not use variables. However, this
is not a limitation and a couple of examples are given that use
variables.

# default sort is sorted by :slot_num, etc. - order of attr_accessor
p Slot.enum.sort.collect:)slot_num)
# => [[1], [3], [4], [8]]

# sort by total errors.
p
Slot.enum.sort(':error_info.sum_total_eoc:)num_errors)').collect:)rate,
':error_info.sum_total_eoc:)num_errors)')
# => [["efec", 1500], ["ufec", 2700], ["ufec", 3000], ["efec", 6000]]

# sort by rate, total errors.
p Slot.enum.sort:)rate,
':error_info.sum_total_eoc:)num_errors)').collect:)rate,
':error_info.sum_total_eoc:)num_errors)')
# => [["efec", 1500], ["efec", 6000], ["ufec", 2700], ["ufec", 3000]]

# sort by rate, total errors, with total errors sorted high to low by
ending with '___' triple underscore.
p Slot.enum.sort:)rate,
':error_info.sum_total_eoc:)num_errors)___').collect:)rate,
':error_info.sum_total_eoc:)num_errors)', :slot_num)
# => [["efec", 6000, 4], ["efec", 1500, 3], ["ufec", 3000, 1], ["ufec",
2700,8]]

I think that the above would be hard to do in a one-liner w/o syntactic
sugar, so this is my 'easy way out' (although I would not be surprised
if you came up with one Robert). I think this is pretty easy and it
saves me time.

The tricky thing I did not consider yet is that you have nested access
to fields and not just one level. In my world this would look like

p Slot.enum.sort_by {|x| [x.rate,
x.error_info.sum_total_eoc:)num_errors)___]}.
map {|x| [x.:rate, x.error_info.sum_total_eoc:)num_errors), x.slot_num]}

That's certainly not nice to squeeze on one line but your solution does
not look that much shorter.
# total errors of all slots.
p Slot.enum.sum_total_eoc(':error_info.sum_total_eoc:)num_errors)')
# => 13200

# slots that have>= 3000 total errors.
p Slot.enum.find_all(':error_info.sum_total_eoc:)num_errors)>=
3000').collect:)slot_num)
# => [[1], [4]]

# use a variable for sort parameter.
var = ':slot_num'
p Slot.enum.sort("#{var}").collect("#{var}")
# => [[1], [3], [4], [8]]

# use a variable w/o interpolation - must pass a binding with enum{}
(which is where this topic started by the way). It is carried along as
an attribute to ArrayEOC and/or EnumeratorEOC, similar to coparables.
var = ':slot_num'
p Slot.enum{}.sort(var).collect(var)
# => [[1], [3], [4], [8]]

# slots that have>= 2500 errors in a single time period.
p Slot.enum.find_all(":error_info.any?:)num_errors>=
2500)").collect:)slot_num)
# => Exceptions galore! my simple 'parser' breaks down. will have touse
the 'old fashioned way' of a code block. maybe I will try to get this to
work in the future.

In case anyone is wondering, if data is input as an Array, it comes out
as an ArrayEOC the first time it is read. In the simplified code above,
I show a call to super in attr_accessor, but really for the reader part,
I add a line that converts an Array to an ArrayEOC. I do this so I can
use the ArrayEOC methods as mofified since I want to keep Array's
methods unmodified.

Since I already use self.inherited, I am able to keep track of
descendants (a recursive jaunt through the child classes using inject to
build an array), so there is enum_only{} which enumerates only the given
class, and enum{} which enumerates the given class and all descendants.
I do this because I have a number of units of similar type which follow
the classic inheritance OO model (even though it is apparent from posts
that this has fallen out of favor). Oh well, it seems that I am behind
the times on a lot of things.

Oh, and since Ruby is dynamic, I had to modify remove_method and
undefine_method to remove any comparables, if applicable (there is a
difference between the two however). And if there are subsequent calls
to attr_accessor, I have to add those methods to the class comparables
in the class and any descendant classes. I allow the user to reset the
comparables to those given in the class and its ancestors. I also tried
to make the code reflective, by making the comparables, child_classes,
descndants, etc. availble.

Your need to redefine remove_method etc. is fallout of your design
decision to change the default ordering. As I said, I believe there are
better and more efficient designs.
Writing this code has certainly been fun and
educational!

That's good!

There are still some things that I didn't yet wrap my head around: why
do you want to make classes keep track of all their instances? This
essentially makes classes global variables - but less obvious. During
the course of a program usually you create instances and let forget them
again. But in your situation you will keep all instances around and
there is no clear demarcation. If you want automated tracking of
instances there are other options, e.g.

class InstanceTracker
def initialize
@all = Hash.new {|h,k| h[k] = []}
end

def new(cl,*args, &b)
@all[cl] << cl.new(*args, &b)
end

def clear
@all.clear
self
end

def clear_class(cl)
@all.delete(cl)
self
end

include Enumerable

def each(&b)
if b
@all.each {|k,v| v.each(&b)}
self
else
Enumerator.new(self, :each)
end
end

def each_class(cl, &b)
if b
@all[cl].each(&b)
self
else
Enumerator.new(self, :each_class, cl)
end
end

def each_sub_classes(cl, &b)
if b
@all.each {|k,v| v.each(&b) if k <= cl}
self
else
Enumerator.new(self, :each_sub_classes, cl)
end
end
end

it = InstanceTracker.new

it.new(Foo, 1, 2)
it.each_class(Foo).find_all {|f| f.size > 0}

etc.

Kind regards

robert
 
J

John Sikora

Robert Klemme wrote in post #947187:
Fluent speaking of Ruby makes it somewhat easier but even for me the
length of the post needs some digesting. So I won't come up with a full
coverage of everything you wrote for now. I hope my remarks are useful
nevertheless.

Your remarks are always useful, and I appreciate them.
This should rather be a EnumerableModifierEOC IMHO since #find is
defined in Enumerable and this module contains all the enumerating
methods which are based on #each (e.g. find_all, inject, map).

True, especially since I also include the module in a subclass of Array,
not just a subclass of Enumerator.
Although no additional methods are shown (these are presented below if I
am not mistaken) the purpose of EnumeratorEOC and ArrayEOC is mainly to
augment standard types with additional functionality it seems. Correct?

Correct. All of the modified methods such as sort, find_all, collect,
all?, etc. are in EnumeratorModifierEOC although I presented only one or
two methods. I originally had modified Array and Enumerator and aliased
the methods for normal code block use (non syntactic sugar), but after
some correspondence in this forum decided that it was not a good idea to
do that. So now I modify the methods in a subclass and call super to
pass on the code block if syntactic sugar (passed parameter string(s) or
symbol(s)) is not used.
A problem of your design is that you change the classes idea of default
ordering. What I mean is this: you need to modify a class to achieve a
particular ordering. Now the standard behavior of sorting has changed...

I do not need to modify a class to achieve a particular ordering, I need
to define a class. The default ordering is set during the class
definition with attr_accessor and set_comparables_order. However, if I
do modify the class (with the same two methods), the default ordering
will change.

Or did you mean that a subclass can / will have a different default sort
order than it's superclass? This can certainly be the case and is
actually encouraged to add flexibility. Are these element of a bad
design? If so, why? (You can be brief, hopefully I will understand.)
...and you can never tell what ordering you will get by only looking at a
particular piece of code which only contains the call to #sort.

True, you would have to look at the attr_accessor and
set_comparables_order methods elsewhere in the code (possibly multiple
places). If these have been modified dynamically, the
ClassName#comparables method can be used to return the default
comparables.
enum.sort_by {|x| [x.field_1, x.field_2]}

which is pretty short, readable and efficient.

When I was looking over the Enumerable methods that I wanted to modify,
I skipped sort_by for some reason. You are right, it is pretty short and
readable. May need to take another look at it.
Your need to redefine remove_method etc. is fallout of your design
decision to change the default ordering. As I said, I believe there are
better and more efficient designs.

True. I said this to make the point that I have thought of things that I
need to do to try to keep things from breaking since Ruby is dynamic. I
think you are saying that by coding this way, I am making it tough on
myself since Ruby is dynamic. Hmmm, need to think about this, because I
know that there will be cases out there that I do not think to cover. I
guess this is a way to tell a good design from a bad one.
There are still some things that I didn't yet wrap my head around: why
do you want to make classes keep track of all their instances?

When I was learing Ruby, I came across ObjectSpace.each_object and
thought that since Ruby makes this method available, why not use it
instead of setting up my own containers? So early versions of the code
used ObjectSpace. Then I discovered self.inherited and class instance
variables. I decided to use self.inherited to pass along the values of
certain class instance variables that I want inherited (and slightly
modified for that subclass). Since keeping track of child classes was
fairly easy with self.inherited and I could also use it to initialize
@class_all_enum_objects, I dropped the use of ObjectSpace. It seems like
ObjectSpace would be less efficient too.
This essentially makes classes global variables - but less obvious.

Never thought of that. See my comments below on my lack of having to
interface with other users' code.
During the course of a program usually you create instances and let forget them
again. But in your situation you will keep all instances around and
there is no clear demarcation.

I do provide a delete method that will delete an EOC object from the
@class_all_enum_objects class instance variable. I also even provide a
recursive delete object method which deletes the EOC object and any EOC
object that it finds as an attribute (for example, a Slot object can
have an attribute :xp which is an Xp object (transponder, in case you
are wondering).
If you want automated tracking of
instances there are other options, e.g.

class InstanceTracker
... lines of code

it = InstanceTracker.new

it.new(Foo, 1, 2)
it.each_class(Foo).find_all {|f| f.size > 0}

I see, but why have a seperate class for this? Aren't you doing the same
thing?

I think that the reason that my code is the way it is, is partially due
to the fact that I am writing my code in isolation; I do
not have to interface to other code to perform a broader function. In
fact, I have no experience writing code with any kind of interface
(explains a lot, huh?). Well, I guess I do use gems, so I am not in
total isolation, and at least I have given it some thought since I
turned away from mofidying Array and Enumerator directly.

js
 
J

John Sikora

By the way, I've been busy and not checking the forums too much lately.
However, I just noticed the concurrent thread of:

sort_by: multiple fields with reverse sort.

http://www.ruby-forum.com/topic/224672#new

In my world I would do:

class B < EnumerableObjectClass::Base
attr_accessor :field_1, :field_0, :field_2 # sets default sort order.

def initialize(field_0, field_1, field_2)
super()
@field_0, @field_1, @field_2 = field_0, field_1, field_2
end
end

B.new("radio", 30, 5)
B.new("radio", 20, 5)
B.new("archie", 20, 5)
B.new("newton", 10, 3)

# default sort - :field_1, field_0, field_2 order, normal direction.
p B.enum{}.sort.collect:)field_0, :field_1, :field_2)
# => [["newton", 10, 3], ["archie", 20, 5], ["radio", 20, 5], ["radio",
30, 5]]

# sort by field_1 reverse, field_0 normal (same as the OP 3rd sort_by in
that thread).
p B.enum{}.sort(':field_1___', :field_0).collect:)field_0, :field_1,
:field_2)
# => [["radio", 30, 5], ["archie", 20, 5], ["radio", 20, 5], ["newton",
10, 3]]

# sort by field_0 reverse, field_2 normal, field_1 reverse.
p B.enum{}.sort(':field_0___', :field_2,
':field_1___').collect:)field_0, :field_1, :field_2)
# => [["radio", 30, 5], ["radio", 20, 5], ["newton", 10, 3], ["archie",
20, 5]]

Easy! Note the triple underscore suffix denotes reverse order sorting
for that parameter. But this code uses the evil eval.

js
 
R

Robert Klemme

In case you get two copies of this: First send attempt was returned by
a spamassassin crash...

Robert Klemme wrote in post #947187:

Your remarks are always useful, and I appreciate them.

Thanks for the feedback!
...

I do not need to modify a class to achieve a particular ordering, I need
to define a class. The default ordering is set during the class
definition with attr_accessor and set_comparables_order. However, if I
do modify the class (with the same two methods), the default ordering
will change.

That's what I mean. =A0Maybe renaming "set_comparables" to something
else is enough (e.g. "attr_sort_order" which IMHO makes it look more
like a keyword like "private"). =A0The name "set_comparables" seems to
suggest that the order can be changed at will. =A0People might be
inclined to do that multiple times in a program and since at any point
in time there is only one such order per class this also poses issues
for multithreaded programs (assuming different ordering would be
employed by different threads). =A0An alternative would be to allow to
call set_comparables at most once per class.
Or did you mean that a subclass can / will have a different default sort
order than it's superclass? This can certainly be the case and is
actually encouraged to add flexibility. Are these element of a bad
design? If so, why? (You can be brief, hopefully I will understand.)

No, the issue I am seeing has nothing to do with inheritance. =A0See above.
True, you would have to look at the attr_accessor and
set_comparables_order methods elsewhere in the code (possibly multiple
places). If these have been modified dynamically, the
ClassName#comparables method can be used to return the default
comparables.

My point is that sort order is something that belongs to a particular
ordering, i.e. the place in code that uses sorting. =A0If you make this
a property of the class which is allowed to change you open your
application for all sorts of nasty effects caused by the fact that
different pieces of code (not necessarily in separate threads) use
that "global variable" in different ways.
True. I said this to make the point that I have thought of things that I
need to do to try to keep things from breaking since Ruby is dynamic. I
think you are saying that by coding this way, I am making it tough on
myself since Ruby is dynamic. Hmmm, need to think about this, because I
know that there will be cases out there that I do not think to cover. I
guess this is a way to tell a good design from a bad one.
:)


When I was learing Ruby, I came across ObjectSpace.each_object and
thought that since Ruby makes this method available, why not use it
instead of setting up my own containers? So early versions of the code
used ObjectSpace. Then I discovered self.inherited and class instance
variables. =A0I decided to use self.inherited to pass along the values of
certain class instance variables that I want inherited (and slightly
modified for that subclass). Since keeping track of child classes was
fairly easy with self.inherited and I could also use it to initialize
@class_all_enum_objects, I dropped the use of ObjectSpace. It seems like
ObjectSpace would be less efficient too.


Never thought of that. See my comments below on my lack of having to
interface with other users' code.

I am not sure whether this is only related to interfacing with foreign
code. =A0Basically my main theme is modularity. =A0By tying tracking
functionality into the classes your design is a tad less modular. =A0One
consequence of this is that since each class is a singleton you can
only ever track all instances of a class in one place. =A0Assuming your
application grows and you need this tracking in different places but
independently you are screwed. =A0If you separate the tracking as I have
tried to demonstrate it's as easy as creating another InstanceTracker.
I see, but why have a seperate class for this? Aren't you doing the same
thing?

From a functionality point of view, yes. =A0But I choose to distribute
the functionality in a different (more modular) way across language
artifacts (classes and methods). =A0Separating concerns is an important
task of a software engineer: all the time when coding we decide where
we place functionality. =A0For small scripts it's OK to lump everything
together. =A0If applications grow you often have to go through a painful
refactoring process to untangle different aspects. =A0If you start out
modular you _may_ have increased effort initially but it pays off mid
to long term. =A0Also, it _can_ help to make code more readable.
I think that the reason that my code is the way it is, is partially due
to the fact that I am writing my code in isolation; I do
not have to interface to other code to perform a broader function. In
fact, I have no experience writing code with any kind of interface
(explains a lot, huh?). Well, I guess I do use gems, so I am not in
total isolation, and at least I have given it some thought since I
turned away from mofidying Array and Enumerator directly.

Well, that's perfectly OK. =A0Software is soft and so we change it over
time. =A0Also, we as humans learn while we go along. =A0My coding
certainly has changed over the years. =A0That's only natural. =A0And
discussing things like these helps in thinking differently about code.
=A0It's a creative process that increases knowledge on all sides.

Kind regards

robert


--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,038
Latest member
OrderProperKetocapsules

Latest Threads

Top