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

Posted by unknown (Guest)
on 13.11.2007 07:40
(Received via mailing list)
The idea of Object#tap is to insert a "listener" (like tapping a phone
line) into the object chain without affecting the object (without
being caught eavesdropping).

On the other hand, we often wish to transform the current object.  I
use Object#as like this: take the current object, name it *as*
something, do something with it, and give the result.

   class Object
      def as
         yield self
      end
   end

Compare:

   all_files2 = transform2(source_files + transform1(
      stems.map { |f|
         f + "." + guess_extension(f)
      })).map { |f|
         File.join(dir, f)
      }

with:

   all_files = stems.map { |f|
      f + "." + guess_extension(f)
   }.as { |t|
      transform2(source_files + transform1(t))
   }.map { |f|
      File.join(dir, f)
   }

The former is so disheveled that I don't even know how to indent it.
On the other hand, the latter is beautiful (to me).

Incidentally I'm all for renaming Object#as to whatever you wish.  I
chose it because it expresses the intent: name the object *as*
something.

--FC
Posted by unknown (Guest)
on 13.11.2007 08:10
(Received via mailing list)
I forgot to include this option:

   t = stems.map { |f|
      f + "." + guess_extension(f)
   }
   all_files3 = transform2(source_files + transform1(t)).map { |f|
      File.join(dir, f)
   }

Introducing needless temporaries on the same scope level as the target
variable makes understanding and maintenance harder.  Look again:

   all_files = stems.map { |f|
      f + "." + guess_extension(f)
   }.as { |t|
      transform2(source_files + transform1(t))
   }.map { |f|
      File.join(dir, f)
   }

This clearly communicates that the purpose of the block chain is to
obtain a value for all_files.  From beginning to end, we understand
the intent at a glance.  In the former case, the intent is muddled by
a useless variable floating around.  Multiply this example by 10 and
it quickly becomes the difference between beauty and travesty.

This was my previous response (with editing) to the suggestion that I
should use Object#instance_eval instead of Object#as:

What if I want to use the "self" before the instance_eval?  What if I
use "self" inside the block while forgetting that I'm inside an
instance_eval?  I'd be screwed, and screwing would serve no purpose
except to avoid defining Object#as.

The use instance_eval communicates a specific purpose which is
entirely different from the purpose of Object#as.  I want to take the
current object, name it *as* something, perform some operations on it,
and give the result.

The current object should not be given the name "self", which is a
special name.  It should be given a temporary name (e.g. "t") which
communicates its temporal non-specialness.  Object#instance_eval is
the former, Object#as is the latter.
Posted by Robert Klemme (Guest)
on 13.11.2007 09:12
(Received via mailing list)
2007/11/13, furtive.clown@gmail.com <furtive.clown@gmail.com>:
> Introducing needless temporaries on the same scope level as the target
> variable makes understanding and maintenance harder.  Look again:

IMHO introducing temporary variables with proper names can go a long
way to make this piece much more readable. (The same holds true for
method names.)

> the intent at a glance.
Frankly, I don't.

>  In the former case, the intent is muddled by
> a useless variable floating around.  Multiply this example by 10 and
> it quickly becomes the difference between beauty and travesty.

Obviously preferences differ.  Although I can only guess what all
those methods do, I would prefer this variant:

all_inputs = source_files +
  transform1( stems.map { |f| f + "." + guess_extension(f) } )
all_files = transform2( all_inputs ).map {|f| File.join dir, f}

Note, if transform2 and transform1 do not need the whole collection I
would change their implementation to transform a single value only
which then would make the whole story a lot simpler.  The current
approach seems quite complex to me but without knowing what all this
is supposed to do I have no better variant to offer.

Kind regards

robert
Posted by unknown (Guest)
on 13.11.2007 10:10
(Received via mailing list)
On Nov 13, 3:12 am, Robert Klemme <shortcut...@googlemail.com> wrote:
>
> IMHO introducing temporary variables with proper names can go a long
> way to make this piece much more readable. (The same holds true for
> method names.)

OK, point taken about the proper name.  Nonetheless I am surprised
this is not a unanimous slam dunk.  The contenders are:

   all_inputs = source_files +
      transform1( stems.map { |f| f + "." + guess_extension(f) } )
   all_files = transform2( all_inputs ).map {|f| File.join dir, f}

verses

   all_files = stems.map { |f|
      f + "." + guess_extension(f)
   }.as { |all_inputs|
      transform2(source_files + transform1(all_inputs))
   }.map { |f|
      File.join dir, f
   }

Since I have been accustomed to using #as for years, the latter looks
canonical and the former looks quirky and too clever.  Three things
come to mind:

(1) Parentheses adjacent to brackets are an eyesore.

(2) The latter is step-by-step linear and easier to follow: one
transformation, then another, then another.  Simple.  The former takes
much more studying in order to understand, relatively speaking.  That
is, my strategy for understanding it is to work from the inmost
expression outward.  But in the latter case, I work linearly from
start to finish.

(3) What is the purpose of all_inputs?  Is it there as an
intermediate, or do you want to use it for something else?  Should I
keep a mental note of it, or should I discard it in my mind?  In the
former case, I am left wondering all of this.  In the latter case the
answer is clear: it's a temporary for building all_files, to be
forgotten as soon as possible.

I am a lone rubyist who went off the grid several years ago after
contributing a project on rubyforge, so perhaps *my* style is the
quirky one.  Still, in my mind the latter is sheer elegance compared
to the former.  I actually find it interesting that someone could
possibly disagree.  Do (1)-(3) make any sense, then?

Regards,
-FC
Posted by Robert Klemme (Guest)
on 13.11.2007 11:25
(Received via mailing list)
2007/11/13, furtive.clown@gmail.com <furtive.clown@gmail.com>:
>       transform1( stems.map { |f| f + "." + guess_extension(f) } )
>    }
>
> Since I have been accustomed to using #as for years, the latter looks
> canonical and the former looks quirky and too clever.  Three things
> come to mind:
>
> (1) Parentheses adjacent to brackets are an eyesore.

I don't subscribe to that. It depends on the individual case.

> (2) The latter is step-by-step linear and easier to follow: one
> transformation, then another, then another.  Simple.  The former takes
> much more studying in order to understand, relatively speaking.  That
> is, my strategy for understanding it is to work from the inmost
> expression outward.  But in the latter case, I work linearly from
> start to finish.

As I said, preferences differ.  Since you are used to working this way
it's easy for you. For me it took quite a bit of time until I
understood the logic.

> (3) What is the purpose of all_inputs?  Is it there as an
> intermediate, or do you want to use it for something else?  Should I
> keep a mental note of it, or should I discard it in my mind?  In the
> former case, I am left wondering all of this.  In the latter case the
> answer is clear: it's a temporary for building all_files, to be
> forgotten as soon as possible.

It never occurred to me that this could be a problem.  If you have too
many temporary variables in a method the method is probably too long
or complex anyway.

> I am a lone rubyist who went off the grid several years ago after
> contributing a project on rubyforge, so perhaps *my* style is the
> quirky one.  Still, in my mind the latter is sheer elegance compared
> to the former.  I actually find it interesting that someone could
> possibly disagree.  Do (1)-(3) make any sense, then?

Partly but I'd rather hear other voices as well.

Btw, what is all this transforming doing?  Can you give a short
description of what this piece of code is supposed to do?  As far as I
can see you have inputs source_files and stems and get a single list
of filenames out of this. Can you give some more hints about the
semantics?

Kind regards

robert
Posted by Daniel DeLorme (Guest)
on 13.11.2007 11:40
(Received via mailing list)
furtive.clown@gmail.com wrote:
> I am a lone rubyist who went off the grid several years ago after
> contributing a project on rubyforge, so perhaps *my* style is the
> quirky one.  Still, in my mind the latter is sheer elegance compared
> to the former.  I actually find it interesting that someone could
> possibly disagree.  Do (1)-(3) make any sense, then?

This kind of suggestion has been made several times in the past and,
while I agree it looks elegant, I believe the issue is that "as" does
not really /belong/ to the object on which it is called. It is a utility
method. If you look at the base methods of Object you'll see that all of
them have to do with the state of the object itself (except for the ugly
exception of "display"). Semantically, a utility method has no business
being part of the public interface of every object. A better solution
would be with(expr){ |var| ... but that kills the elegance doesn't it? I
sympathize, I really do. I have the same issue with my pet method "ergo"

Lately I've been thinking that this could tie into Ara's concept of
"pervasive" methods. If we define a pervasive method as a method that
applies to all objects, then we could do something like
     Pervasives.define do
       def as
         yield(self)
       end
     end
Which could be called reliably as:
     Pervasives.call(expr, :as){ |obj| ... }
or less reliably through method_missing:
     expr.as{ |obj| ... }
leaving the Object methodspace unpolluted:
     expr.methods.include?("as") #=> false

no?

Daniel
Posted by Robert Klemme (Guest)
on 13.11.2007 11:49
(Received via mailing list)
2007/11/13, Daniel DeLorme <dan-ml@dan42.com>:
> method. If you look at the base methods of Object you'll see that all of
>        def as
> no?
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
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..

Cheers

robert
Posted by Daniel DeLorme (Guest)
on 13.11.2007 12:39
(Received via mailing list)
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*?

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

Daniel
Posted by Robert Klemme (Guest)
on 13.11.2007 12:59
(Received via mailing list)
2007/11/13, Daniel DeLorme <dan-ml@dan42.com>:
> 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... :-)

> 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
Posted by unknown (Guest)
on 13.11.2007 14:45
(Received via mailing list)
On Nov 13, 5:39 am, Daniel DeLorme <dan...@dan42.com> wrote:
> This kind of suggestion has been made several times in the past and,
> while I agree it looks elegant, I believe the issue is that "as" does
> not really /belong/ to the object on which it is called. It is a utility
> method. If you look at the base methods of Object you'll see that all of
> them have to do with the state of the object itself (except for the ugly
> exception of "display"). Semantically, a utility method has no business
> being part of the public interface of every object.

I think we are generally on the same page, but I would add that to_a,
to_ary, to_hash, to_enum, to_s are utilities as well since they just
output stuff.  If these conversion utilities were not part of the
class then there would be a lot less chaining going on, and
consequently a lot fewer happy programmers.  Ruby got that one right,
even though the fuddy-duddies object to the mutual tying between
classes for the mere sake of convenience.

Therefore it would be consistent to also desire Object#to_arg, another
conversion utility (a.k.a "as") which puts the thing into a block
argument, just as Hash#to_a puts the thing into an array.  (The
analogy is a little forced but still convincing.)  Ruby has already
committed itself to making happy programmers instead of happy language
theoreticians --- why not go this one step further?  Somewhere out
there is a ruby programmer who, though he may not know it now, will be
made happier by it.

A second, separate argument is that the addition of Object#tap should
imply the addition of its complement, Object#as (or Object#to_arg, or
whatever we call it).  A rubyist should notice something inconsistent
in being able to yield an object to a block without affecting the
object, but being unable to yield an object to a block in order to
produce a result.

--FC
Posted by ara.t.howard (Guest)
on 13.11.2007 20:05
(Received via mailing list)
On Nov 13, 2007, at 12:10 AM, furtive.clown@gmail.com wrote:

> variable makes understanding and maintenance harder.  Look again:
>
>    all_files = stems.map { |f|
>       f + "." + guess_extension(f)
>    }.as { |t|
>       transform2(source_files + transform1(t))
>    }.map { |f|
>       File.join(dir, f)
>    }
>

the very real problem with this sort of thing is that exceptions will
map to one fugly line, making debugging for some poor soul a
nightmare.  imagine that 'guess_extension' returned nil, or
'transform2' for that matter.  this kind of golfing is great for
perl, but i've found over many painful experiences that it offers
little in the way of clarity or maintainability in production code.
i think at least a few ruby hackers, hardened by sunday night
debugging sessions, and anyone fleeing perl, will agree that
something along the lines of

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

is clear, maintainable, and easy to understand even if you don't
dream of lambda calculus.  come back six months later and add
transform3 and you'll appreciated what i'm talking about

and of course this isn't even addressing the issue that anyone
*really* naming a function transform1 or transform2 should be
smothered in their sleep, nor the fact that such functionality
should, in something as beautiful as ruby, end up looking like

   filelist = Filelist.for source_files, :stems => stems, :dir => dir

making the pimpl nearly *irrelevant* so long as it's clear and allows
easy debugging and testing - testing being another *huge* checkmark
against monolithic method chains.

> This clearly communicates that the purpose of the block chain is to
> obtain a value for all_files.  From beginning to end, we understand
> the intent at a glance.  In the former case, the intent is muddled by
> a useless variable floating around.  Multiply this example by 10 and
> it quickly becomes the difference between beauty and travesty.

i strongly disagree: variable names are one of the single most
important elements of writing maintainable code - unless you happen
to be one of the very few who loves writing documentation (i
certainly don't).  variables being references in ruby, i consider it
something almost evil *not* to through in a well named variable where
it lends clarity by literally spelling out the programmer's intent,
cuts line length, makes transitioning or modifying the code later
vastly easier, and stacktraces actually meaningful

none of this is to say that an Object#as or Object#tap is a bad idea
or that it always makes things less clear.  but it's really a stretch
to say it makes the above logic clear.  i used to think

   for(i = j = k = 0; i < irank(matrix) && j < jrank(matrix) && krank
(matrix); i++, j++, k++){
     ...

was clever, and now it makes me want to run screaming into the woods
yelling 'internal iterator god damn it!'

so my concern with #as and #tap is that the lend strong support to
bad programming practices - making what should be hidden and internal
exposed and external.

kind regards.

a @ http://codeforpeople.com/
Posted by Ryan Davis (Guest)
on 13.11.2007 20:33
(Received via mailing list)
I agree with the whole of Ara's response and was debating whether to
respond to the OP at all... Ara obviates that desire. But, I did have
to respond to this:

On Nov 13, 2007, at 11:03 , ara.t.howard wrote:

> and of course this isn't even addressing the issue that anyone  
> *really* naming a function transform1 or transform2 should be  
> smothered in their sleep,

OMG I simply love you for this Ara... I totally agree that we need
more smothering going on. ;)

> nor the fact that such functionality should, in something as  
> beautiful as ruby, end up looking like
>
>  filelist = Filelist.for source_files, :stems => stems, :dir => dir

Exactly...

The real "need" for #as comes from a even more real need to clean up
one's design in the first place. Do that and #as simply drops away.
Posted by Daniel Waite (rabbitcreative)
on 13.11.2007 20:38
ara.t.howard wrote:
> the very real problem with this sort of thing is that exceptions will
> map to one fugly line, making debugging for some poor soul a
> nightmare.  imagine that 'guess_extension' returned nil, or
> 'transform2' for that matter.  this kind of golfing is great for
> perl, but i've found over many painful experiences that it offers
> little in the way of clarity or maintainability in production code.
> i think at least a few ruby hackers, hardened by sunday night
> debugging sessions, and anyone fleeing perl, will agree that
> something along the lines of
> 
>    guesses = stems.map{|stem| "#{ stem }.#{ guess_extension stem }"}
>    basenames = transform2 source_files + transform1(guesses)
>    expanded = basenames.map{|basename| File.join dir basename}
> 
> is clear, maintainable, and easy to understand even if you don't
> dream of lambda calculus.  come back six months later and add
> transform3 and you'll appreciated what i'm talking about
> 
> and of course this isn't even addressing the issue that anyone
> *really* naming a function transform1 or transform2 should be
> smothered in their sleep, nor the fact that such functionality
> should, in something as beautiful as ruby, end up looking like
> 
>    filelist = Filelist.for source_files, :stems => stems, :dir => dir

*claps* Beautiful. Absolutely beautiful.

At first I dug what the OP was talking about with #as and also found it 
a bit odd that it wasn't a "slam dunk" for others.

But Ara's examples explain _why_ there was no slam dunk. #as is cool, 
but Ara's suggestions are simply good programming.

If I had to choose between any of the examples given (whether OP, JEII, 
etc.) I'd go with what Ara has put forth.

Off-topic, are there any Smalltalkers here who feel there's a boundary 
between object-oriented programming and functional programming? Can the 
two (do the two) coexist?
Posted by James Gray (bbazzarrakk)
on 13.11.2007 20:47
(Received via mailing list)
On Nov 13, 2007, at 1:38 PM, Daniel Waite wrote:

>>
>> smothered in their sleep, nor the fact that such functionality
>> should, in something as beautiful as ruby, end up looking like
>>
>>    filelist = Filelist.for source_files, :stems => stems, :dir => dir
>
> *claps* Beautiful. Absolutely beautiful.

Yeah, I hate to send in a "me too" post, but heck:

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
Posted by Daniel Waite (rabbitcreative)
on 13.11.2007 21:22
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?
Posted by unknown (Guest)
on 13.11.2007 21:25
(Received via mailing list)
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
      }
   }

Of course transform1 and transform2 are bad names --- they are meant
to be bad!  They are intentionally generic for the purpose of the
example.  They stand for any function.

You gave the Filelist example as the preferred code, which is exactly
right.  However we are not talking about Filelist, but the
*implementation* of Filelist.

Could you clarify how the above code lends "strong support to bad
programming practices"?  That seems unfounded.  On the contrary, I
find it more appealing at least because (1) the intent of the code ---
to produce a value for 'expanded' --- is more obvious; (2) there are
no temporaries lying around which may potentially cause confusion in
future maintenance; (3) the logic flow is more obvious: three
consecutive operations, with the output of one fed into the input of
the next.

Exceptionally Kind Regards,
-FC
Posted by James Gray (bbazzarrakk)
on 13.11.2007 21:28
(Received via mailing list)
On Nov 13, 2007, at 2:22 PM, Daniel Waite wrote:

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

Ara knows and sees all?  ;)

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

James Edward Gray II
Posted by -a (Guest)
on 13.11.2007 23:07
(Received via mailing list)
(replying via google groups - sorry for any formatting issues)


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

well although that may sound heretical to me (vim user ;-)) i was
thinking more along the lines of debugging production code where you
typically just have some logs or, as i'm currently doing, debugging
stacktraces hand written and walked out of a classified facility!
(seriously)


>       basenames.map { |basename|
>          File.join dir, basename
>       }
>    }
>
> Of course transform1 and transform2 are bad names --- they are meant
> to be bad!  They are intentionally generic for the purpose of the
> example.  They stand for any function.

i know.  still, i couldn't resist.

>
> You gave the Filelist example as the preferred code, which is exactly
> right.  However we are not talking about Filelist, but the
> *implementation* of Filelist.
>

fair enough.

> Could you clarify how the above code lends "strong support to bad
> programming practices"?  That seems unfounded.  On the contrary, I
> find it more appealing at least because (1) the intent of the code ---
> to produce a value for 'expanded' --- is more obvious; (2) there are
> no temporaries lying around which may potentially cause confusion in
> future maintenance; (3) the logic flow is more obvious: three
> consecutive operations, with the output of one fed into the input of
> the next.

for the record, i would use #as just as i use #eval and co.  as far as
clarity goes, maybe you come from a functional background, but i've
never found stringing lambdas together as very clear.  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.

anyhow - eval, instance_eval, class << self, are all tools that are
fugly, dangerous, and let us do things that probably should not be
aired in the open.  #as, #returning, and company seem, to me, are
meant more as annotations, for example,

  def foobar
    returning Object.new do |object|  ### hey!  i'm returning an
object

but can easily obscure code too as scopes (potentially) and names
mogrify a few times in a line or two when they are chained.

i guess in the end i'm being more critical of the particular example
you gave rather than #as on the whole - but #as just seems to have a
little code smell too it since it loses much of it's power inside a
small method where local vars get popped anyhow

so

  def foobar
    tmp = self
    someting_with tmp
  end

vs

  def foobar
    self.as{|tmp| something_with tmp}
  end

is obviously not much in the way of gained clarity.

it just seems like it's *only* in longish method chains begging for a
re-factoring that it really shines.  i'd really have to use it anger
to decide.


cheers.
Posted by Daniel DeLorme (Guest)
on 14.11.2007 01:49
(Received via mailing list)
ara.t.howard wrote:
> 
> the very real problem with this sort of thing is that exceptions will 
> map to one fugly line, making debugging for some poor soul a nightmare.  

That's just false. The backtrace will have the correct line number, no
matter how many lines an expression spans.

> and of course this isn't even addressing the issue that anyone *really* 
> naming a function transform1 or transform2 should be smothered in their 
> sleep

+1, no one will argue with that, but it's unrelated to the issue at
hand. If your purpose was to poison the well[1] by pouring ridicule on a
tangential topic, shame on you. If not, I apologize.

> i strongly disagree: variable names are one of the single most important 
> elements of writing maintainable code - unless you happen to be one of 
> the very few who loves writing documentation (i certainly don't).  
> variables being references in ruby, i consider it something almost evil 
> *not* to through in a well named variable where it lends clarity by 
> literally spelling out the programmer's intent, cuts line length, makes 
> transitioning or modifying the code later vastly easier, and stacktraces 
> actually meaningful

Well it's a good thing that you like variable names because Object#as is
*all* about using variables. So let's rewrite the example to use
*descriptive* variables names (and less lines, you seem to like less 
lines):

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

In the end, this is not a question of golfing or clarity or
maintainability or bad programming practices. It's simply a question of
style: imperative vs. functional. The only objective difference is that
using Object#as the temporary variable you create is isolated within its
own scope. The rest is an entirely subjective difference of style.

Daniel

[1] http://www.nizkor.org/features/fallacies/poisoning-the-well.html
Posted by unknown (Guest)
on 14.11.2007 14:45
(Received via mailing list)
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.)

I almost editorialized the headings as "Temporaries floating around
randomly, obscuring the target all_files," and "Temporaries tucked
away safely inside block chains, leaving the lone target all_files for
all of us to see."

> > I have not had such problems with exception traces.  Emacs parses the
> > trace, so I've never had a problem tracking exceptions down.
>
> well although that may sound heretical to me (vim user ;-)) i was
> thinking more along the lines of debugging production code where you
> typically just have some logs or, as i'm currently doing, debugging
> stacktraces hand written and walked out of a classified facility!
> (seriously)

I use emacs with vi bindings; you might say I'm the product of a mixed
marriage.  I get Hanukkah presents *and* Christmas presents,
metaphorically speaking.

The stacktrace argument holds no water with me.  Is there a ruby bug
in the line-number reporting?  I don't see the issue here.

> for the record, i would use #as just as i use #eval and co.  as far as
> clarity goes, maybe you come from a functional background, but i've
> never found stringing lambdas together as very clear.

This is part of my motivation here.  The block-chains above are
beautiful to me.  Concise, clear, and everything else.

Several years ago I experimented with writing ruby in a functional
style where it seemed appropriate.  I loved the results.  There is a
lot to say here.  In short, I became a better ruby programmer (which I
didn't think was possible!).  By functional style I mean functional
style in the small, such as inside the definition of a method.
Thinking in terms of transformations, removing or reducing side-
effects --- well, I won't get into it now.

>
That's not an example of functional style.  That's using a local
variable verses not doing so.  It has nothing to do with functional
style.

Part of your response attempts to conflate #as with #eval,
#instance_eval, #returning, singleton objects, and perhaps also the
kitchen sink.  However we are talking about #as.  I do not accept this
argument-by-association.  If you wish to make an argument against #as,
you are obligated to address #as directly.

> i guess in the end i'm being more critical of the particular example
> you gave rather than #as on the whole - but #as just seems to have a
> little code smell too it since it loses much of it's power inside a
> small method where local vars get popped anyhow

On the contrary, every local variable removed from the target
variable's scope is a win.  Even if it's one variable, that's a win.
That's one less distraction to the purpose of the code.  Or two, such
as in the example above.

If a programmer is making huge, complex method definitions and refuses
to split them up, then there's nothing we can do about it.  Your
argument seems to be, "Well, with #as, those huge definitions will
become more manageable, and therefore #as will encourage the
programmer not to split them up."  It is difficult to explain the
ridiculousness in that line of thinking.  In the meantime, good
programmers will correctly use smaller definitions while benefiting
from #as (or, at least I benefit from it).

>   end
Straw man.  Nobody in their right mind would do that.  That's not an
argument against #as.

I have yet to see a legitimate argument against #as here, save for the
clutter argument which also applies to #tap etc as well.  I can
appreciate that to some degree, yet in many years I have not seen one
case of an actual problem arising from it.

My point is inherently difficult to make because it requires an
appreciation of method chaining and block-chaining.  In the larger
context, an appreciation of functional style is also involved.  So,
try it out for six months: if you still don't like it after that, then
you can return it for a full refund.
Posted by Robert Klemme (Guest)
on 14.11.2007 15:34
(Received via mailing list)
2007/11/14, furtive.clown@gmail.com <furtive.clown@gmail.com>:
>   }
>    }.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
Posted by unknown (Guest)
on 14.11.2007 17:10
(Received via mailing list)
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?

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
Posted by James Gray (bbazzarrakk)
on 14.11.2007 17:19
(Received via mailing list)
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.

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
Posted by Trans (Guest)
on 14.11.2007 17:43
(Received via mailing list)
On Nov 13, 3:25 pm, furtive.cl...@gmail.com wrote:
>    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.
Posted by Daniel DeLorme (Guest)
on 15.11.2007 04:57
(Received via mailing list)
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
Posted by ara.t.howard (Guest)
on 15.11.2007 05:27
(Received via mailing list)
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.

regards.

a @ http://codeforpeople.com/
Posted by Robert Klemme (Guest)
on 15.11.2007 21:06
(Received via mailing list)
On 15.11.2007 05:26, ara.t.howard wrote:
> 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
Posted by Sean O'halpin (sean)
on 16.11.2007 01:17
(Received via mailing list)
On Nov 13, 2007 10:05 PM, -a <ara.t.howard@gmail.com> wrote:

>
> 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
Posted by Robert Klemme (Guest)
on 16.11.2007 11:14
(Received via mailing list)
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

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
Posted by unknown (Guest)
on 16.11.2007 20:21
(Received via mailing list)
On Nov 15, 3:04 pm, Robert Klemme <shortcut...@googlemail.com> wrote:
>
> }
>
> 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
Posted by ara.t.howard (Guest)
on 16.11.2007 22:40
(Received via mailing list)
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...

a @ http://codeforpeople.com/
Posted by David A. Black (Guest)
on 16.11.2007 22:56
(Received via mailing list)
Hi --

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

>
> 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
Posted by ara.t.howard (Guest)
on 16.11.2007 23:07
(Received via mailing list)
On Nov 15, 2007, at 1:05 PM, Robert Klemme wrote:

> 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/
Posted by Sean O'halpin (sean)
on 17.11.2007 03:23
(Received via mailing list)
On Nov 16, 2007 10:12 AM, Robert Klemme <shortcutter@googlemail.com> 
wrote:
> 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. ;)
Posted by -a (Guest)
on 17.11.2007 04:03
(Received via mailing list)
On Nov 16, 2:55 pm, "David A. Black" <dbl...@rubypal.com> wrote:
> >> if response
>
> When would it ever be empty?
>
> David
>
>

sorry - through a compact in there...

personally i prefer the '=' sign in there...
Posted by Robert Klemme (Guest)
on 17.11.2007 11:33
(Received via mailing list)
2007/11/16, ara.t.howard <ara.t.howard@gmail.com>:
> > end it's mainly a question of scoping: whether you need multiple
>    def let *a, &b
>
>        instance_eval = Instance_eval.bind let
> a = 40
> p let(:c => a, :d => b){ c + d }
>          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