From: Josh Cheek Date: 2012-09-14T18:46:15+09:00 Subject: Re: inject is pathetic? --047d7b675e606667ce04c9a64b5c Content-Type: text/plain; charset=ISO-8859-1 On Thu, Sep 13, 2012 at 10:00 PM, Matt Neuburg wrote: > Dave Aronson wrote: > > > On Wed, Sep 12, 2012 at 11:46 PM, 7stud -- wrote: > > > > > Just forget inject() even exists. It's really quite a pathetic method. > > > > What do you mean by that? > > He probably just doesn't grasp how fundamental it is. After all, no one > would call "join" pathetic, but it is simply a special case of inject. > Or, to put it a better way, inject abstracts intuitively obvious notions > such as join, sum, and product. And in Ruby 1.9.3 it gets even cooler, > since you can just say: > > [1,2,3].inject(:+) > > I disagree. Nearly everything you want to use inject for, each_with_object is better. Array#join, for example is better implemented using each_with_object than inject strings.each_with_object("") { |current, joined| joined << current } Perhaps in a language like Haskell where strings are just lists of characters, then you could join them in a single pass, if you started at the end and worked backwards. Or perhaps the thunk could be smart enough to compose or allow access to it without creating all the intermediate forms. But in Ruby strings.inject("") { |joined, current| joined + current } is slow and wasteful because it created all the intermediate forms: "", "a", "ab", "abc", "abcd", "abcde" Whereas each_with_object only creates: "abcde" Your example is compelling to you, because you're enamored with the implementation, but `[1,2,3].inject(:+)` is ugly, confusing, and buggy. If we could say something like `[1,2,3].sum_from 0`, this would be clearer because we don't care about the implementation. When we see `.inject(:+)` we have to mentally translate it "oh, we're getting the sum". That cost adds up, things like this should be declarative. It's buggy because if your array is empty, you get `[].inject(:+) # => nil`, but the null object for a sum is 0, so you have to remember to pass the initial argument `[].inject(0, :+) # => 0` (that param should probably be required). It's further ugly because it breaks from Ruby conventions. When you have a method that takes a block, everywhere else you would have to say `[].inject(0, &:+)`, but here, translating the symbol into a block is moved into the method, causing you to think you have a bug until you go read the docs enough times to remember this one-off case. And in the few cases where inject does make more sense than each_with_object, it may be clearer to just iterate over the array with #each, and update a local variable. > Not everyone knows LISP or has read The Structure and Interpretation of > Computer Programs. Those who have, know. m. > > I generally like Lisp, but the elitism around it is just obnoxious, as in this quote (at least for Common Lisp and Scheme, Clojure seems more down to earth). --047d7b675e606667ce04c9a64b5c Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable On Thu, Sep 13, 2012 at 10:00 PM, Matt Neuburg <matt@tidbits.com> wrote:
Dave Aronson <rubytalk2dave@davearonson.com> wrote:

> On Wed, Sep 12, 2012 at 11:46 PM, 7stud -- <lists@ruby-forum.com> wrote:
>
> > Just forget inject() even exists. =A0It's really quite a path= etic method.
>
> What do you mean by that?

He probably just doesn't grasp how fundamental it is. After all, = no one
would call "join" pathetic, but it is simply a special case of in= ject.
Or, to put it a better way, inject abstracts intuitively obvious notions such as join, sum, and product. And in Ruby 1.9.3 it gets even cooler,
since you can just say:

=A0 =A0[1,2,3].inject(:+)


I disagree. Nearly everything you want to use inj= ect for, each_with_object is better. Array#join, for example is better impl= emented using each_with_object than inject

strings.each_with_object(= "") { |current, joined| joined << current }

Perhaps in a language like Haskell where strings are just lists of char= acters, then you could join them in a single pass, if you started at the en= d and worked backwards. Or perhaps the thunk could be smart enough to compo= se or allow access to it without creating all the intermediate forms. But i= n Ruby

strings.inject("") { |joined, current| joined + current }
=
is slow and wasteful because it created all the intermediate forms: &qu= ot;", "a", "ab", "abc", "abcd"= , "abcde"
Whereas each_with_object only creates: "abcde"



You= r example is compelling to you, because you're enamored with the implem= entation, but `[1,2,3].inject(:+)` is ugly, confusing, and buggy. If we cou= ld say something like `[1,2,3].sum_from 0`, this would be clearer because w= e don't care about the implementation. When we see `.inject(:+)` we hav= e to mentally translate it "oh, we're getting the sum". That = cost adds up, things like this should be declarative. It's buggy becaus= e if your array is empty, you get `[].inject(:+) # =3D> nil`, but the nu= ll object for a sum is 0, so you have to remember to pass the initial argum= ent `[].inject(0, :+) # =3D> 0` (that param should probably be required)= . It's further ugly because it breaks from Ruby conventions. When you h= ave a method that takes a block, everywhere else you would have to say `[].= inject(0, &:+)`, but here, translating the symbol into a block is moved= into the method, causing you to think you have a bug until you go read the= docs enough times to remember this one-off case.

And in the few cases where inject does make more sense than each_with_o= bject, it may be clearer to just iterate over the array with #each, and upd= ate a local variable.

=A0
Not everyone knows LISP or has read The Structure and Interpretation of
Computer Programs. Those who have, know. m.


I generally like Lisp, but the elitism around it is just obnoxi= ous, as in this quote (at least for Common Lisp and Scheme, Clojure seems m= ore down to earth).
--047d7b675e606667ce04c9a64b5c--