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

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

···

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]

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

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]

···

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

Martin DeMello 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.

···

On 12/17/06, Daniel Finnie <danfinnie@optonline.net> wrote:

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

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]

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

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

···

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

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

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]

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

···

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.

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

(...)

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

···

--
Pedro Fortuny Ayuso
C/Capuchinos 14, 1. 47006 Valladolid. SPAIN