Enumerable#build

M

Martin DeMello

class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect into
non-array structures (binary trees, e.g.) as long as they implement a
suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed} right
now but a #build method makes the common case pretty.)

martin
 
R

Robert Klemme

Martin said:
class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect
into non-array structures (binary trees, e.g.) as long as they
implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :)

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| add(yield(*a))}
self
end
end

class Hash
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| send:)[]=, *yield(*a))}
self
end
end
h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

Kind regards

robert
 
S

Sean O'Halpin

class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build =3D=3D Array#<<
Hash#build(k,v) =3D=3D Hash#[]
etc

Not sure you tested this! :) Maybe something like this:

module Enumerable
def build(seed)
each {|i|
seed.add(*(yield i))
}
seed
end
end

class Hash
alias :add :store
end
class Array
def add(*args)
self << args
end
end

p [1,2,3].build({}) {|i| [i, 1]}
p [1,2,3].build([]) {|i| [i, 1]}

---------- Output ----------
{1=3D>1, 2=3D>1, 3=3D>1}
[[1, 1], [2, 1], [3, 1]]


Regards,

Sean
 
M

Martin DeMello

Robert Klemme said:
Martin said:
class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect
into non-array structures (binary trees, e.g.) as long as they
implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :)

- You should add seed as return value.

Oops - yes, I was enthused about the idea and got sloppy with the
actual code.
- Your first example won't work as Hash#add is nonexistent

No, that was my point - add a #add method (chosen not to conflict with
anything currently in the core) to any class that you wish to act as a
build "client".
An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| add(yield(*a))}
self
end
end

class Hash
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| send:)[]=, *yield(*a))}
self
end
end
h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I'd make one change to your way - keep Addable separate from Enumerable.
Once we've done that, it's simple to add both #populate to Addable
(requires an Enumerable argument) and #build to Enumerable (regretting,
once again, that #collect is already a synonym of #map). One nice thing
is that classes implementing both Enumerable and Addable could then
provide a "structural map" - e.g.

class Hash
def smap
build({}) {|k,v| [k, yield(v)]}
end
end

{:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}

Or even something more complex like

class Tree
def each
...
yield [element, path]
end

def add args
element, path = args
...
end

def smap
build(Tree.new) {|e, path| [(yield e), path }
# or
# Tree.new.populate(self) {|e, path| [(yield e), path]}
end
end

martin
 
R

Robert Klemme

Martin said:
h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I'd make one change to your way - keep Addable separate from
Enumerable.

What exactly do you mean by Addable? Did I overlook something? If you
just mean method #add then of course you have to keep them separate
because every container must implement it's own version od #add. Btw, the
name doesn't seem to be well chosen for Hash because not every element is
added, i.e. for duplicate keys the second one isn't exactly added.
Once we've done that, it's simple to add both #populate
to Addable (requires an Enumerable argument) and #build to Enumerable
(regretting, once again, that #collect is already a synonym of #map).

Problem I see here is that requirements of Enumerable are extended: now
it's "your class needs to implement each" then it's "your class needs to
implement each and if you want to use build/populate your class also needs
to implement #add (or whatever it's called)".
One nice thing is that classes implementing both Enumerable and
Addable could then provide a "structural map" - e.g.

class Hash
def smap
build({}) {|k,v| [k, yield(v)]}
end
end

{:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}
{:x=>2, :y=>4}.inject({}) {|h,(k,v)| h[k]=v*v;h}
=> {:x=>4, :y=>16}

This doesn't look too bad - and it's not restricted to building hashes. I
think your #smap is not general enough. I also start doubting whether
build / populate are actually good ideas...
Or even something more complex like

class Tree
def each
...
yield [element, path]
end

def add args
element, path = args
...
end

def smap
build(Tree.new) {|e, path| [(yield e), path }
# or
# Tree.new.populate(self) {|e, path| [(yield e), path]}
end
end

Btw, I'd prefer the idiom

def smap
build(self.class.new) ...
....
end

Kind regards

robert
 
E

Eric Hodel

Martin said:
class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question that
prompted the to_hash thread, but also, in general, you can collect
into non-array structures (binary trees, e.g.) as long as they
implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :)

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| add(yield(*a))}
self
end
end

class Hash
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| send:)[]=, *yield(*a))}
self
end
end

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I think you have too much magic.

Enumerable#to_a, Array#fill, Array#flatten and Hash::[] do this
pretty handily and much more cleanly:

Hash[*ary.fill { |i| [i, "val #{i}"]}.flatten]

$ ruby -e 'p Hash[*(0..3).to_a.fill { |i| [i, "val #{i}"] }.flatten ]'
{0=>"val 0", 1=>"val 1", 2=>"val 2", 3=>"val 3"}
 
R

Robert Klemme

Eric said:
Martin said:
class Enumerable
def build(seed)
each {|i|
seed.add(yield i)
}
end
end

and

Array#build == Array#<<
Hash#build(k,v) == Hash#[]
etc

So you could say [1,2,3].build({}) {|i| [i, 1]} for the question
that prompted the to_hash thread, but also, in general, you can
collect into non-array structures (binary trees, e.g.) as long as
they implement a suitable #add method.

(Of course, you can do ary.inject(seed) {|i| seed.add(f(i)); seed}
right now but a #build method makes the common case pretty.)

Looks good. But,

- Enumerable is a module... :)

- You should add seed as return value.

- Your first example won't work as Hash#add is nonexistent

An alternative approach would be to reverse the logic and have
Enumerable#populate:

module Enumerable
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| add(yield(*a))}
self
end
end

class Hash
def populate(*enum)
enum = enum.first if enum.size == 1
enum.each {|*a| send:)[]=, *yield(*a))}
self
end
end

h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I think you have too much magic.

Is this good or bad? :))
Enumerable#to_a, Array#fill, Array#flatten and Hash::[] do this
pretty handily and much more cleanly:

Hash[*ary.fill { |i| [i, "val #{i}"]}.flatten]

$ ruby -e 'p Hash[*(0..3).to_a.fill { |i| [i, "val #{i}"] }.flatten ]'
{0=>"val 0", 1=>"val 1", 2=>"val 2", 3=>"val 3"}

I'm not sure about cleanness but this is certainly less memory efficient
as it creates relatively large temporary data structures.

Kind regards

robert
 
M

Martin DeMello

Robert Klemme said:
Martin said:
h={}.populate(1,2,3) {|x| [x, "val #{x}"]}
=> {1=>"val 1", 2=>"val 2", 3=>"val 3"}

What do you think?

I'd make one change to your way - keep Addable separate from
Enumerable.

What exactly do you mean by Addable? Did I overlook something? If you
just mean method #add then of course you have to keep them separate
because every container must implement it's own version od #add. Btw, the
name doesn't seem to be well chosen for Hash because not every element is
added, i.e. for duplicate keys the second one isn't exactly added.

I mean a collection of methods (like #populate) that rely on the
existence of #add in the receiver.
Problem I see here is that requirements of Enumerable are extended: now
it's "your class needs to implement each" then it's "your class needs to
implement each and if you want to use build/populate your class also needs
to implement #add (or whatever it's called)".

Which is why I postulated a separate Addable mixin. #build would be
under Enumerable (receiver implements #each, argument implements #add),
and #populate under Addable (vice versa).
One nice thing is that classes implementing both Enumerable and
Addable could then provide a "structural map" - e.g.

class Hash
def smap
build({}) {|k,v| [k, yield(v)]}
end
end

{:x=>2, :y=>4}.smap {|i| i*i} #=> {:x => 4, :y => 16}
{:x=>2, :y=>4}.inject({}) {|h,(k,v)| h[k]=v*v;h}
=> {:x=>4, :y=>16}

This doesn't look too bad - and it's not restricted to building hashes. I
think your #smap is not general enough. I also start doubting whether
build / populate are actually good ideas...

Not general enough how? The intention is to replicate a 'structure'
while transforming its elements. Even if build/populate aren't good
ideas, the underlying problem (that #map flattens its receiver into a
linear list) remains, and ought to be solved.
Btw, I'd prefer the idiom

def smap
build(self.class.new) ...
...
end

Yeah, that's nice. Can be implemented properly inside a mixin, then.
Perhaps with included hooks in both Enumerable and Addable to check if
the other one is already included, and to define smap if so.

Incidentally, there's an excellent FP paper titled "The Underappreciated
Unfold", which hurts my brain, but is well worth a read. There's a copy
available here:
http://www.itee.uq.edu.au/~ck/Papers/Program Transformation/folds etc/unfold.ps

martin
 

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

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,169
Latest member
ArturoOlne
Top