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
on 13.11.2007 07:40
on 13.11.2007 08:10
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.
on 13.11.2007 09:12
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
on 13.11.2007 10:10
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
on 13.11.2007 11:25
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
on 13.11.2007 11:40
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
on 13.11.2007 11:49
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
on 13.11.2007 12:39
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
on 13.11.2007 12:59
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
on 13.11.2007 14:45
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
on 13.11.2007 20:05
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/
on 13.11.2007 20:33
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.
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?
on 13.11.2007 20:47
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
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?
on 13.11.2007 21:25
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
on 13.11.2007 21:28
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
on 13.11.2007 23:07
(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.
on 14.11.2007 01:49
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
on 14.11.2007 14:45
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.
on 14.11.2007 15:34
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
on 14.11.2007 17:10
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
on 14.11.2007 17:19
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
on 14.11.2007 17:43
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.
on 15.11.2007 04:57
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
on 15.11.2007 05:27
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/
on 15.11.2007 21:06
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
on 16.11.2007 01:17
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
on 16.11.2007 11:14
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
on 16.11.2007 20:21
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
on 16.11.2007 22:40
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/
on 16.11.2007 22:56
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
on 16.11.2007 23:07
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/
on 17.11.2007 03:23
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. ;)
on 17.11.2007 04:03
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...
on 17.11.2007 11:33
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