Just some Ruby language ideas, comments wanted

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

Daniel F. 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

Devin M. 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 V.

James Edward G. II wrote:

On Dec 16, 2006, at 1:11 PM, David V. 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 V.

David V. wrote:

Devin M. 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

On Dec 16, 2006, at 1:11 PM, David V. 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 G. II

On Sun, Dec 17, 2006 at 04:00:49AM +0900, Devin M. wrote:

the pain. Besides, what if the above array contained nil?

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
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]

(…)

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.

Wow, I was just about going to suggest the very same thing, using the
indices
instead of the values is necessary quite frequently (at least I find it
is
so). The
name of the method ought to be a bit informative:

indices_with
indices_such
indices_having
select_indices

I think.

I really think this is useful enough to be included in the language.

Thanks for the suggestion and eager to help if necessary,

Pedro

I did something like that for the current Ruby Q., 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]

On 12/17/06, Daniel F. [email protected] 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

Martin DeMello wrote:

On 12/17/06, Daniel F. [email protected] 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.

First, Sorry for the late reply.

Without using additional variables and LOCs, I don’t see a way for your
code (even the 1.9 version) to change the nth nil statement as is
possible with the ArrayValue class.

Dan

On 12/17/06, Devin M. [email protected] 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