Yielding an object and caring about the result: the cousin of Object#tap

R

Robert Klemme

2007/11/14 said:
Let's compare them again. I changed some variable names which will
hopefully remove that red herring from the conversation. I also made
the styles more consistent for better comparison. (I was going to
show both my style and your one-liner style, but it was too
distracting.)

Temporaries in the scope of the target all_files:

data_files = stems.map { |stem|
"#{ stem }.#{ guess_extension stem }"
}
basenames =
add_suffixes(source_files + add_prefixes(data_files))
all_files = basenames.map { |basename|
File.join dir, basename
}

Temporaries inside block chains:

all_files = stems.map { |stem|
"#{ stem }.#{ guess_extension stem }"
}.as { |data_files|
add_suffixes(source_files + add_prefixes(data_files))
}.map { |basename|
File.join dir, basename
}

(BTW 'suffix' here means the chars right before the dot; after the dot
I call the extension.)

Why do add_suffixes and add_prefixes have to be methods that work on a
collection instead of a single item? Do they modify the collection in
any way or do they need information from other entries to do their
work on a single entry?

If not, I would change that and then you could do this:

all_files = (
source_files +
stems.map {|stem| add_prefix "#{ stem }.#{ guess_extension stem }"}
).map {|name| File.join dir, add_suffix(name)}

This is still pretty complex (but the algorithm is anyway) and you do
not need any temporaries outside of block parameters. Also,
add_suffix and add_prefix will be relieved from iterating collections
and can be focussed on doing one thing properly.

Kind regards

robert
 
F

furtive.clown

Why do add_suffixes and add_prefixes have to be methods that work on a
collection instead of a single item? Do they modify the collection in
any way or do they need information from other entries to do their
work on a single entry?

add_prefixes and add_suffixes examine the array; they cannot be
optimized by acting on a single element instead. I previously called
them transform1 and transform2 in order to emphasize that we can't
assume anything about those methods.

A red herring was thrown into the discussion when I was chided for
calling them transform1 and transform2. Now that I've changed the
names, another red herring has emerged.

Please, could we resist the temptation to change the example code
based upon assumptions about the given input and methods? Such
pursuits are not at all relevant. This is what sidetracked the last
thread.

-FC
 
J

James Edward Gray II

Please, could we resist the temptation to change the example code
based upon assumptions about the given input and methods? Such
pursuits are not at all relevant.

Is it possible for you to give us a real example, that functions as
intended and is complete in code, that isn't subject to such
refactorings? Maybe that would help us focus on the question at hand.

James Edward Gray II
 
T

Trans

Ara,

I have not had such problems with exception traces. Emacs parses the
trace, so I've never had a problem tracking exceptions down.

You said you disagreed about variable names, but I didn't make an
argument about variable names. The argument was about needless
temporaries lying around and mucking up at-a-glance comprehension.
Those temporaries can be culled away nicely with Object#as.

expanded = stems.map { |stem|
"#{ stem }.#{ guess_extension stem }"
}.as { |guesses|
transform2(source_files + transform1(guesses))
}.as { |basenames|
basenames.map { |basename|
File.join dir, basename
}
}

what the heck is wrong with

guesses = stems.map { |stem| "#{ stem }.#{ guess_extension
stem }" }
basenames = transform2(source_files + transform1(guesses))
expanded = basenames.map { |basename| File.join dir, basename }

or if you prefer

expanded = (
guesses = stems.map { |stem| "#{ stem }.#{ guess_extension
stem }" }
basenames = transform2(source_files + transform1(guesses))
basenames.map { |basename| File.join dir, basename }
)

T.
 
D

Daniel DeLorme

James said:
Is it possible for you to give us a real example, that functions as
intended and is complete in code, that isn't subject to such
refactorings? Maybe that would help us focus on the question at hand.

But you can *always* refactor code to fit a particular style. FC wants
to talk about the usefulness of Object#as in functional-style code and
Robert insists that Object#as is not needed in imperative code. We
clearly have a cultural barrier with the 2 sides talking past each other.

Daniel
 
A

ara.t.howard

But you can *always* refactor code to fit a particular style. FC
wants to talk about the usefulness of Object#as in functional-style
code and Robert insists that Object#as is not needed in imperative
code. We clearly have a cultural barrier with the 2 sides talking
past each other.

this seems pretty close to the mark. i realized that, for me, #as
seems a bit too functional but, as i mentioned, i just need to use it
a bit to decide.

regards.

a @ http://codeforpeople.com/
 
R

Robert Klemme

this seems pretty close to the mark. i realized that, for me, #as
seems a bit too functional but, as i mentioned, i just need to use it
a bit to decide.

Interesting point although I am not sure whether #as is really
functional (and thus, whether Daniel's remark is really close to the
mark). If I think of functional programming paradigm first item that
comes to mind is lack of side effects (other than IO probably). Here,
#as just enables method chaining in a single statement where you would
otherwise need multiple statements. From my point of view this is not
functional vs. non functional. In the end it's mainly a question of
scoping: whether you need multiple local variables in the current scope
or can achieve the same with temporaries in nested and thus smaller
scopes. FC has stressed this point several times.

IMHO the (or at least: my) discussion was about the point that FC
claimed his solution with #as to be more readable than another solution
that used local variables for temporary values. So I tried to come up
with solutions that *I* consider more readable to be able to contrast
them with his approach. Note that I do not expect everybody to find
them equally readable since this is subject to personal preference and
habit.

Here is another approach that completely gets rid of temporaries without
needing #as:

all_files =
add_suffixes(source_files +
add_prefixes(
stems.map { |stem| "#{stem}.#{guess_extension stem}" }
)).map { |basename|
File.join dir, basename
}

If anything this seems more functional to me, since you have to read
inside out. :) SCNR

Cheers

robert
 
S

Sean O'Halpin

i also typically prefer

response = http.response
response.foo
response.bar

to

http.response.foo
http.response.bar

for two reasons

1) error reporting
2) vars are cheap in ruby, function calls are not

although for simple/short code i do use the second often, reserving
the former for 'real' code i anticipate maintaining and debugging. i
suspect i'm in the minority here.

Me too, though that may be an ancient aversion to null pointers in
structure member dereferencing in C as in

some->null_pointer->in_the_middle->of_a_chain

I also find myself using this pattern:

if response = http.response # i.e. expecting nil or false on failure
response.foo
# etc.
else
...
end

Your point on avoiding unnecessary method calls is well-made - there's
no telling how much an intermediate call may cost - why repeat it?

Regards,
Sean
 
R

Robert Klemme

2007/11/16 said:
I also find myself using this pattern:

if response = http.response # i.e. expecting nil or false on failure
response.foo
# etc.
else
...
end

I seriously dislike the pattern above for the following reasons: it
can be easily misread as "if response == http.response" and there is
no need to have an assignment inside an if condition. In this case I
prefer an extra variable setter or using "and" if there is just one
statement:

response = http.response

if response
...
end

response = http.response and puts response

The situation is different with loops because there often this is the
most elegant solution:

while line = gets
...
end

Cheers

robert
 
F

furtive.clown

Here is another approach that completely gets rid of temporaries without
needing #as:

all_files =
add_suffixes(source_files +
add_prefixes(
stems.map { |stem| "#{stem}.#{guess_extension stem}" }
)).map { |basename|
File.join dir, basename

}

If anything this seems more functional to me, since you have to read
inside out. :) SCNR

Whether the function call syntax is prefix, infix, or postfix is
independent of whether it's functional style or not. One reason I
like ruby is because I can be functional with postfix syntax. I
prefer reading block chains and method chains from beginning to end,
as opposed to prefix-syntax function calls from inside to outside.
Object#as allows me to keep the chain going when I am forced to use a
prefix syntax like File.basename().

I would also add that I make temporaries all the time. I do however
avoid re-assigning to the same temporary and, in most circumstances,
modifying the contents of a temporary (to be ditched when practical
concerns for efficiency are relevant).

-FC
 
D

David A. Black

Hi --

response = http.response

if response
...
end

this just jumped into mind

unless [ response = http.response ].empty?
p response.body.size
end

i've never used that, but it's kind of interesting...

When would it ever be empty?


David
 
A

ara.t.howard

Interesting point although I am not sure whether #as is really
functional (and thus, whether Daniel's remark is really close to
the mark). If I think of functional programming paradigm first
item that comes to mind is lack of side effects (other than IO
probably). Here, #as just enables method chaining in a single
statement where you would otherwise need multiple statements. From
my point of view this is not functional vs. non functional. In the
end it's mainly a question of scoping: whether you need multiple
local variables in the current scope or can achieve the same with
temporaries in nested and thus smaller scopes. FC has stressed
this point several times.

i guess it reminds of 'let' a little...

this sprang to mind:

cfp:~ > cat a.rb
class Object
def let *a, &b
Let.evaluate *a, &b
end

def as local, kvs = {}, &block
kvs.update local => self
Let.evaluate kvs, &block
end

class Let
instance_methods.each{|m| undef_method m unless m[%r/^__/]}

Instance_eval = Object.instance_method :instance_eval
Instance_variable_set =
Object.instance_method :instance_variable_set

def self.evaluate kvs = {}, &block
let = new
singleton_class =
class << let
self
end
instance_eval = Instance_eval.bind let
instance_variable_set = Instance_variable_set.bind let
singleton_class.module_eval{ kvs.keys.each{|k| attr k} }
instance_eval.call{ kvs.each{|k,v| instance_variable_set.call
"@#{ k }", v} }
instance_eval.call &block
end
end
end


a = 40
b = 2
c = 'forty-two'

p let:)x => a, :y => b){ x + y }

p a.as:)x){ x + b }

p a.as:)x, :y => b){ x + y }

# fatal flaw
p let:)c => a, :d => b){ c + d }



cfp:~ > ruby a.rb
42
42
42
a.rb:44:in `+': can't convert Fixnum into String (TypeError)
from a.rb:44
from a.rb:27:in `instance_eval'
from a.rb:27:in `call'
from a.rb:27:in `evaluate'
from a.rb:3:in `let'
from a.rb:44


still - it's kind of interesting...






a @ http://codeforpeople.com/
 
S

Sean O'Halpin

2007/11/16 said:
I also find myself using this pattern:

if response = http.response # i.e. expecting nil or false on failure
response.foo
# etc.
else
...
end

I seriously dislike the pattern above for the following reasons: it
can be easily misread as "if response == http.response" and there is
no need to have an assignment inside an if condition.
[snip]

The situation is different with loops because there often this is the
most elegant solution:

while line = gets
...
end

Well, I don't really see the difference myself - but then I am
conditioned by C so I tend not to mix = and == up.
However, if you think it's a little tricksy, then I'll certainly
consider changing my practice. After all, code is written for other
people, not for yourself.

Regards,
Sean
use.inject do |as, often| as.you_can - without end

I do agree, but you know, that could be taken the wrong way (cf.
ruby-talk:277479 ff. ;)
 
R

Robert Klemme

2007/11/16 said:
Interesting point although I am not sure whether #as is really
functional (and thus, whether Daniel's remark is really close to
the mark). If I think of functional programming paradigm first
item that comes to mind is lack of side effects (other than IO
probably). Here, #as just enables method chaining in a single
statement where you would otherwise need multiple statements. From
my point of view this is not functional vs. non functional. In the
end it's mainly a question of scoping: whether you need multiple
local variables in the current scope or can achieve the same with
temporaries in nested and thus smaller scopes. FC has stressed
this point several times.

i guess it reminds of 'let' a little...

this sprang to mind:

cfp:~ > cat a.rb
class Object
def let *a, &b
Let.evaluate *a, &b
end

def as local, kvs = {}, &block
kvs.update local => self
Let.evaluate kvs, &block
end

class Let
instance_methods.each{|m| undef_method m unless m[%r/^__/]}

Instance_eval = Object.instance_method :instance_eval
Instance_variable_set =
Object.instance_method :instance_variable_set

def self.evaluate kvs = {}, &block
let = new
singleton_class =
class << let
self
end
instance_eval = Instance_eval.bind let
instance_variable_set = Instance_variable_set.bind let
singleton_class.module_eval{ kvs.keys.each{|k| attr k} }
instance_eval.call{ kvs.each{|k,v| instance_variable_set.call
"@#{ k }", v} }
instance_eval.call &block
end
end
end


a = 40
b = 2
c = 'forty-two'

p let:)x => a, :y => b){ x + y }

p a.as:)x){ x + b }

p a.as:)x, :y => b){ x + y }

# fatal flaw
p let:)c => a, :d => b){ c + d }



cfp:~ > ruby a.rb
42
42
42
a.rb:44:in `+': can't convert Fixnum into String (TypeError)
from a.rb:44
from a.rb:27:in `instance_eval'
from a.rb:27:in `call'
from a.rb:27:in `evaluate'
from a.rb:3:in `let'
from a.rb:44


still - it's kind of interesting...

Well, you could of course just do

$ ruby -e 'def let;yield;end; let { x=10;y=20; puts x+y}'
30
$ ruby -e 'x=10;y=20; puts x+y'
30

:)

I guess "let" is in Ruby far less useful than in Lisp. :)

Kind regards

robert
 

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,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top