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

Ara knows and sees all? :wink:

Or perhaps: Don't let fancy programming get in the way of good programming.

James Edward Gray II

ยทยทยท

On Nov 13, 2007, at 2:22 PM, Daniel Waite wrote:

James Gray wrote:

Me too!

Ara communicated everything I have been trying to say far better than
I did or even could have. I need to print his message out and put it
on the wall in my office.

James Edward Gray II

No worries there, James. I, too would like to post this somewhere
(writing a blog entry right now), but after trying to quote him, the
text suddenly feels lacking the punch I felt when I first read it. I
think this is due, in part, to the lack of context.

Do you think we can extract a general principle from what he's said?
Maybe something akin to the "tips" in The Pragmatic Programmer?

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

ยทยทยท

2007/11/14, furtive.clown@gmail.com <furtive.clown@gmail.com>:

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

--
use.inject do |as, often| as.you_can - without 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

ยทยทยท

2007/11/16, Sean O'Halpin <sean.ohalpin@gmail.com>:

I also find myself using this pattern:

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

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

Robert Klemme wrote:
> Hmm... But this has the drawback that - since it's not defined on
> Object - it's difficult to be aware of this method. Whether it's

It doesn't need to be explicitly defined on Object if you know that it's
*pervasive* i.e. by definition available on all objects. Actually the
same could be extended to all /methods|instance_var/ methods. Don't you
ever do obj.methods and find yourself wishing all those were not there
to clutter the bigger picture of what the object *does*?

Actually not, because I can do obj.public_methods(false). Also, if I
want to know what a particular method does I go to the documentation
anyway.

> defined explicitly or called implicitly via method_missing all objects
> will properly respond to it. I am not sure I understand the benefit
> of not explicitly defining it..

Indeed the behavior would be pretty much the same. The biggest
difference is semantics I guess. Utility methods do not belong to the
public interface, and therefore calling them through the public
interface should be seen as mere synctatic sugar. A thin line to draw, I
agree.

I had to wipe my glasses before I could spot it... :slight_smile:

Also the distinction between pseudo-public utility methods and
true public methods may be a mere artifact of my mind, as 1.9 now
includes tap, to_enum, and enum_for, which *definitely* classify as
utility methods in my mind.

I have to say I like #to_enum very much but I agree that there is the
issue of cluttering namespaces. But IMHO no matter what you do (i.e.
explicit or implicit definition) the root problem does not go away
unless things like selector namespaces come into existence (i.e. a
particular view of a class / object in a context). As long as that
does not exist there is always potential for name clashes no matter
what.

Cheers

robert

ยทยทยท

2007/11/13, Daniel DeLorme <dan-ml@dan42.com>:

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

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

ยทยทยท

On Nov 14, 9:33 am, Robert Klemme <shortcut...@googlemail.com> wrote:

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?

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

a @ http://codeforpeople.com/

ยทยทยท

On Nov 16, 2007, at 3:12 AM, Robert Klemme wrote:

response = http.response

if response
  ...
end

--
it is not enough to be compassionate. you must act.
h.h. the 14th dalai lama

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

ยทยทยท

On Nov 16, 2007 10:12 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

2007/11/16, Sean O'Halpin <sean.ohalpin@gmail.com>:

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

ยทยทยท

On Nov 14, 2007, at 10:10 AM, furtive.clown@gmail.com wrote:

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.

Hi --

ยทยทยท

On Sat, 17 Nov 2007, ara.t.howard wrote:

On Nov 16, 2007, at 3:12 AM, Robert Klemme wrote:

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

--
Upcoming training by David A. Black/Ruby Power and Light, LLC:
   * Advancing With Rails, Berlin, Germany, November 19-22
   * Intro to Rails, London, UK, December 3-6 (by Skills Matter)
See http://www.rubypal.com for details!

James Edward Gray II wrote:

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.

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

sorry - through a compact in there...

personally i prefer the '=' sign in there...

ยทยทยท

On Nov 16, 2:55 pm, "David A. Black" <dbl...@rubypal.com> wrote:

Hi --

On Sat, 17 Nov 2007, ara.t.howard wrote:

> On Nov 16, 2007, at 3:12 AM, Robert Klemme wrote:

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

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/

ยทยทยท

On Nov 14, 2007, at 8:57 PM, Daniel DeLorme wrote:

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.

--
share your knowledge. it's a way to achieve immortality.
h.h. the 14th dalai lama

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. :slight_smile: SCNR

Cheers

  robert

ยทยทยท

On 15.11.2007 05:26, ara.t.howard wrote:

On Nov 14, 2007, at 8:57 PM, Daniel DeLorme wrote:

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.

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

ยทยทยท

On Nov 15, 3:04 pm, Robert Klemme <shortcut...@googlemail.com> wrote:

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. :slight_smile: SCNR

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

ยทยทยท

On Nov 15, 2007, at 1:05 PM, Robert Klemme wrote:

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.

--
we can deny everything, except that we have the possibility of being better. simply reflect on that.
h.h. the 14th dalai lama

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

:slight_smile:

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

Kind regards

robert

ยทยทยท

2007/11/16, ara.t.howard <ara.t.howard@gmail.com>:

On Nov 15, 2007, at 1:05 PM, Robert Klemme wrote:

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

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