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

Discussion in 'Ruby' started by furtive.clown@gmail.com, Nov 13, 2007.

  1. Guest

    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
     
    , Nov 13, 2007
    #1
    1. Advertising

  2. Guest

    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.
     
    , Nov 13, 2007
    #2
    1. Advertising

  3. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    2007/11/13, <>:
    >
    > 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:


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

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


    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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Nov 13, 2007
    #3
  4. Guest

    On Nov 13, 3:12 am, Robert Klemme <> 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
     
    , Nov 13, 2007
    #4
  5. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    2007/11/13, <>:
    > On Nov 13, 3:12 am, Robert Klemme <> 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.


    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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Nov 13, 2007
    #5
  6. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    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
     
    Daniel DeLorme, Nov 13, 2007
    #6
  7. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    2007/11/13, Daniel DeLorme <>:
    > 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?


    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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Nov 13, 2007
    #7
  8. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    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
     
    Daniel DeLorme, Nov 13, 2007
    #8
  9. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    2007/11/13, Daniel DeLorme <>:
    > 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

    --
    use.inject do |as, often| as.you_can - without end
     
    Robert Klemme, Nov 13, 2007
    #9
  10. Guest

    On Nov 13, 5:39 am, Daniel DeLorme <> 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
     
    , Nov 13, 2007
    #10
  11. ara.t.howard Guest

    Re: Yielding an object and caring about the result: the cousin ofObject#tap

    On Nov 13, 2007, at 12:10 AM, wrote:

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


    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/
    --
    it is not enough to be compassionate. you must act.
    h.h. the 14th dalai lama
     
    ara.t.howard, Nov 13, 2007
    #11
  12. Ryan Davis Guest

    Re: Yielding an object and caring about the result: the cousin ofObject#tap

    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.
     
    Ryan Davis, Nov 13, 2007
    #12
  13. Daniel Waite Guest

    Re: Yielding an object and caring about the result: the cous

    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 via http://www.ruby-forum.com/.
     
    Daniel Waite, Nov 13, 2007
    #13
  14. Re: Yielding an object and caring about the result: the cous

    On Nov 13, 2007, at 1:38 PM, Daniel Waite wrote:

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


    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
     
    James Edward Gray II, Nov 13, 2007
    #14
  15. Guest

    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
     
    , Nov 13, 2007
    #15
  16. Daniel Waite Guest

    Re: Yielding an object and caring about the result: the cous

    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 via http://www.ruby-forum.com/.
     
    Daniel Waite, Nov 13, 2007
    #16
  17. Re: Yielding an object and caring about the result: the cous

    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?


    Ara knows and sees all? ;)

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

    James Edward Gray II
     
    James Edward Gray II, Nov 13, 2007
    #17
  18. -a Guest

    Re: Yielding an object and caring about the result: the cousin ofObject#tap

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


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


    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.
     
    -a, Nov 13, 2007
    #18
  19. Re: Yielding an object and caring about the result: the cousin ofObject#tap

    ara.t.howard wrote:
    >> 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)
    >> }

    >
    > 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
     
    Daniel DeLorme, Nov 14, 2007
    #19
  20. Guest

    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.

    > i also typically prefer
    >
    > response = http.response
    > response.foo
    > response.bar
    >
    > to
    >
    > http.response.foo
    > http.response.bar
    >


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

    >
    > def foobar
    > tmp = self
    > someting_with tmp
    > end
    >
    > vs
    >
    > def foobar
    > self.as{|tmp| something_with tmp}
    > 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.
     
    , Nov 14, 2007
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Calvin Spealman

    Ideas for yielding and exception raising

    Calvin Spealman, Jun 5, 2004, in forum: Python
    Replies:
    3
    Views:
    324
    Peter Hansen
    Jun 8, 2004
  2. Talin

    Yielding a chain of values

    Talin, Aug 28, 2005, in forum: Python
    Replies:
    18
    Views:
    441
  3. (Jamie Andrews)

    C parser yielding syntax tree data structure?

    (Jamie Andrews), Apr 8, 2006, in forum: C Programming
    Replies:
    7
    Views:
    515
  4. raylopez99
    Replies:
    9
    Views:
    546
  5. Michael Tan
    Replies:
    32
    Views:
    1,021
    Ara.T.Howard
    Jul 21, 2005
Loading...

Share This Page