[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Just some Ruby language ideas, comments wanted.

Daniel Finnie

12/16/2006 6:46:00 PM

My first Ruby language idea is simple. Instead of having every method
take a default value, do the following:
class Object
def default(*args)
self
end
end

class NilClass
def default(arg)
arg # And have a block form, too.
end
end

How this would work:
Currently, the following would default to "i" if the element doesn't exist.
[4, 6, 9].fetch(4, "i")
This would do the same thing:
[4, 6, 9][4].default("i") or [4, 6, 9].fetch(4).default("i")

It would reduce a lot of repeated code in the actual Ruby codebase and
would give more room for parameters to functions (because we don't
really have named parameters).

Plus, the Ruby Way is not to do:
retVal = defaultValue if retVal.nil?

And this would solve that.

-------

My second idea is a lot more out there but, I feel, still a good idea.
A form of all the classes with Enumerable mixed in (hash, array, etc)
where all the methods like select return not the values that were
selected but an object that contains a reference back to the index of
the array where it was stored.

This would allow things like:
ary = [4, nil, 6, nil]
ary.select{|x| x.nil?}[1].set(2)
ary # => [4, nil, 6, 2]

As it is now, the above wouldn't work because select returns an array of
values from the array, so calling set only changes the value in the
array that was created by set.

With my idea, select would return an "ArrayValue" (or something) class,
and the set parameter of that class would update the original array.

The ArrayValue class could be implemented as such:
class ArrayValue
def initialize(origArray, origIndex)
@origArray, @origIndex = origArray, origIndex
end

def get
@origArray[@origIndex]
end

def set(newObj)
@origArray[@origIndex] = newObj
end

# more methods, like delete, etc could be implemented.
end

There are some problems to this right now, mainly:
-Have to use a method to set, not the un-overloadable = lanugage construct.
-Need some disambiguation between methods that return values and methods
that return ArrayValues (maybe everything returns an ArrayValue and one
must use ArrayValue#get).
-Fundamental Redesign

Despite these problems, I see a lot of use cases for ArrayValue.

Thanks for reading (that long thing),
Dan

11 Answers

Devin Mullins

12/16/2006 7:01:00 PM

0

Daniel Finnie wrote:
> How this would work:
> Currently, the following would default to "i" if the element doesn't exist.
> [4, 6, 9].fetch(4, "i")
> This would do the same thing:
> [4, 6, 9][4].default("i") or [4, 6, 9].fetch(4).default("i")
I get along fine with:
[4, 6, 9].fetch(4)||'i'

Sure, it has false misses on instances of FalseClass, but I live with
the pain. Besides, what if the above array contained nil?

> This would allow things like:
> ary = [4, nil, 6, nil]
> ary.select{|x| x.nil?}[1].set(2)
> ary # => [4, nil, 6, 2]
May I suggest:
ary.select{|x| x.nil?}[1] = 2
as a better interface.

= isn't overridable, but []= is.

I see more potential in your second thing... Build it as a stand-alone
Ruby library first.

Devin

David Vallner

12/16/2006 7:12:00 PM

0

Devin Mullins wrote:
>> This would allow things like:
>> ary = [4, nil, 6, nil]
>> ary.select{|x| x.nil?}[1].set(2)
>> ary # => [4, nil, 6, 2]
> May I suggest:
> ary.select{|x| x.nil?}[1] = 2
> as a better interface.
>

This wouldn't work with the current semantics of select, since select
returns a filtered copy of the array.

I'm not sure I'd like if select returned some subclass of array that
overrides []= to propagate changes back to the original one. First and
foremost, potential massive code breakage without solving an actual use
case. Secondly, it's emulating poor man's variable references that
doesn't quite fit into Ruby. (Overall I don't see the use of this sort
of collection view pattern much.)

Maybe as part of Enumerator or Facets or a similar Library Of Somewhat
Generic Spiffy Things, but not in the standard library, and definately
not in a codebreaking way.

David Vallner

James Gray

12/16/2006 7:22:00 PM

0

On Dec 16, 2006, at 1:11 PM, David Vallner wrote:

> Maybe as part of Enumerator or Facets or a similar Library Of Somewhat
> Generic Spiffy Things, but not in the standard library...

I'm not sure what you are saying here. Enumerator is a standard
library is Ruby 1.8 and promoted to the language core in Ruby 1.9.

James Edward Gray II

David Vallner

12/16/2006 8:49:00 PM

0

James Edward Gray II wrote:
> On Dec 16, 2006, at 1:11 PM, David Vallner wrote:
>
>> Maybe as part of Enumerator or Facets or a similar Library Of Somewhat
>> Generic Spiffy Things, but not in the standard library...
>
> I'm not sure what you are saying here. Enumerator is a standard library
> is Ruby 1.8 and promoted to the language core in Ruby 1.9.
>

Yes, but it is a library. I admit my phrasing was wrong, I just wouldn't
like the "collection view" features Enumerator might or might not have
be the default behaviour for #select and the like.

David Vallner

Devin Mullins

12/16/2006 9:43:00 PM

0

David Vallner wrote:
> Devin Mullins wrote:
>>May I suggest:
>> ary.select{|x| x.nil?}[1] = 2
>>as a better interface.
>
> This wouldn't work with the current semantics of select, since select
> returns a filtered copy of the array.

Yeah, my bad, lemme re-suggest something like:
ary.as_references.select {|x| x.nil? }[1] = 2
or:
ary.select_refs {|x| x.nil? }[1] = 2

Devin

Logan Capaldo

12/16/2006 9:48:00 PM

0

On Sun, Dec 17, 2006 at 04:00:49AM +0900, Devin Mullins wrote:
> Daniel Finnie wrote:
> >How this would work:
> >Currently, the following would default to "i" if the element doesn't exist.
> >[4, 6, 9].fetch(4, "i")
> >This would do the same thing:
> >[4, 6, 9][4].default("i") or [4, 6, 9].fetch(4).default("i")
> I get along fine with:
> [4, 6, 9].fetch(4)||'i'
>
> Sure, it has false misses on instances of FalseClass, but I live with
> the pain. Besides, what if the above array contained nil?
>
> >This would allow things like:
> >ary = [4, nil, 6, nil]
> >ary.select{|x| x.nil?}[1].set(2)
> >ary # => [4, nil, 6, 2]
> May I suggest:
> ary.select{|x| x.nil?}[1] = 2
> as a better interface.
>
> = isn't overridable, but []= is.
>
> I see more potential in your second thing... Build it as a stand-alone
> Ruby library first.
>
> Devin
Here, I'll evenget you started:

% cat view.rb
class ArrayView
class ArrayIndexRef
def initialize( array, index )
@array = array
@index = index
end

def value
@array[@index]
end

def value=(new_value)
@array[@index] = new_value
end
end

def initialize( array )
@array = array
@references = []
end

def [](*args)
if args.length == 1 and args.kind_of? Range or args.length > 1
@references[*args].map { |x| x.value }
else
@references[*args].value
end
end

def []=(index, value)
@references[index].value = value
end

def each
@references.each do |x|
yield x.value
end
end


def add_ref( index )
@references << ArrayIndexRef.new( @array, index )
end
end


class Array
def select_view
r = ArrayView.new( self )
each_with_index do |item, index|
r.add_ref( index ) if yield( item )
end
r
end
end

a = (1..10).to_a

p a
b = a.select_view { |x| (x % 2).zero? }
b[0] = 42
p a

% ruby view.rb
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 42, 3, 4, 5, 6, 7, 8, 9, 10]

Daniel Finnie

12/16/2006 11:00:00 PM

0

I did something like that for the current Ruby Quiz, but it doesn't do
the []=, which I think is cool. The code is a bit shorter though:
class ArrayValue
instance_methods.each do |m|
undef_method(m) unless m =~ /^_*(method_missing|send|id)_*$/
end

def initialize(origArray, origIndex)
@origArray, @origIndex = origArray, origIndex
end

def set(newObj)
@origArray[@origIndex] = newObj
end

def get
@origArray[@origIndex]
end

def method_missing(method, *args)
get.send(method, *args)
rescue
super
end
end

class Array
def to_av()
ret = []
each_index {|x| ret << ArrayValue.new(self, x) }
ret
end
end

Sample usage:
daniel@daniel-desktop:~$ irb -r 'arrayvalue.rb'

irb(main):001:0> ary = [1, 2, 3, 4, "SomeString", :ASymbol]
=> [1, 2, 3, 4, "SomeString", :ASymbol]

# Set the first String to 42.
irb(main):002:0> ary.to_av.find{|x| x.kind_of?(String)}.set(42)
=> 42

# Changes are reflected in the original array.
irb(main):003:0> ary
=> [1, 2, 3, 4, 42, :ASymbol]

# Set the 3rd Numeric value to 43
irb(main):004:0> ary.to_av.select{|x| x.kind_of?(Numeric)}[2].set(43)
=> 43

# Changes are reflected in the original array.
irb(main):005:0> ary
=> [1, 2, 43, 4, 42, :ASymbol]

Logan Capaldo wrote:
> On Sun, Dec 17, 2006 at 04:00:49AM +0900, Devin Mullins wrote:
>> Daniel Finnie wrote:
>>> How this would work:
>>> Currently, the following would default to "i" if the element doesn't exist.
>>> [4, 6, 9].fetch(4, "i")
>>> This would do the same thing:
>>> [4, 6, 9][4].default("i") or [4, 6, 9].fetch(4).default("i")
>> I get along fine with:
>> [4, 6, 9].fetch(4)||'i'
>>
>> Sure, it has false misses on instances of FalseClass, but I live with
>> the pain. Besides, what if the above array contained nil?
>>
>>> This would allow things like:
>>> ary = [4, nil, 6, nil]
>>> ary.select{|x| x.nil?}[1].set(2)
>>> ary # => [4, nil, 6, 2]
>> May I suggest:
>> ary.select{|x| x.nil?}[1] = 2
>> as a better interface.
>>
>> = isn't overridable, but []= is.
>>
>> I see more potential in your second thing... Build it as a stand-alone
>> Ruby library first.
>>
>> Devin
> Here, I'll evenget you started:
>
> % cat view.rb
> class ArrayView
> class ArrayIndexRef
> def initialize( array, index )
> @array = array
> @index = index
> end
>
> def value
> @array[@index]
> end
>
> def value=(new_value)
> @array[@index] = new_value
> end
> end
>
> def initialize( array )
> @array = array
> @references = []
> end
>
> def [](*args)
> if args.length == 1 and args.kind_of? Range or args.length > 1
> @references[*args].map { |x| x.value }
> else
> @references[*args].value
> end
> end
>
> def []=(index, value)
> @references[index].value = value
> end
>
> def each
> @references.each do |x|
> yield x.value
> end
> end
>
>
> def add_ref( index )
> @references << ArrayIndexRef.new( @array, index )
> end
> end
>
>
> class Array
> def select_view
> r = ArrayView.new( self )
> each_with_index do |item, index|
> r.add_ref( index ) if yield( item )
> end
> r
> end
> end
>
> a = (1..10).to_a
>
> p a
> b = a.select_view { |x| (x % 2).zero? }
> b[0] = 42
> p a
>
> % ruby view.rb
> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
> [1, 42, 3, 4, 5, 6, 7, 8, 9, 10]
>
>

Martin DeMello

12/17/2006 10:41:00 AM

0

On 12/17/06, Daniel Finnie <danfinnie@optonline.net> wrote:
>
> This would allow things like:
> ary = [4, nil, 6, nil]
> ary.select{|x| x.nil?}[1].set(2)
> ary # => [4, nil, 6, 2]

This will work in 1.8:

class Array
def map_if!(condition)
map! {|i| condition.call(i) ? yield i : i}
end
end

ary.map_if! (lambda {|i| i.nil?) {|x| replacement for x}

1.9 allows blocks to take other blocks as arguments, which will (I
think!) allow something like

class Array
def map_if!(&blk)
_map_if(blk)
end

def _map_if(blk)
map! {|i|
blk.call(i) ? yield i : i
}
end
end

ary.map_if {|x| x.nil?} {|x| do something with x}

Seems more generally useful than setting selected elements by index
(internal versus external iterator)

martin

Devin Mullins

12/17/2006 12:38:00 PM

0

Martin DeMello wrote:
> On 12/17/06, Daniel Finnie <danfinnie@optonline.net> wrote:
>
>>
>> This would allow things like:
>> ary = [4, nil, 6, nil]
>> ary.select{|x| x.nil?}[1].set(2)
>> ary # => [4, nil, 6, 2]
>
> Seems more generally useful than setting selected elements by index
> (internal versus external iterator)
Ah, but to get access to the 2nd nil, yours requires an external
counter*, whereas select_view can go both ways (potentially):
ary.select{|x| x % 2 == 0}.map! {|x| x / 2}

(I'd provide a patch, but it's early.)
Devin

*or a combination of enum_for(:each_with_index) and a non-destructive
version of map_if defined in Enumerable.

Martin DeMello

12/17/2006 3:30:00 PM

0

On 12/17/06, Devin Mullins <twifkak@comcast.net> wrote:
> Ah, but to get access to the 2nd nil, yours requires an external
> counter*, whereas select_view can go both ways (potentially):
> ary.select{|x| x % 2 == 0}.map! {|x| x / 2}
>
> (I'd provide a patch, but it's early.)
> Devin
>
> *or a combination of enum_for(:each_with_index) and a non-destructive
> version of map_if defined in Enumerable.

Yeah, if you specifically require the nth element returned by select,
then an external cursor is the way to go. I just cannot think of any
case where you would.

martin