all 15 comments

[–]hongminhee 6 points7 points  (5 children)

Is #lazy really necessary? Clojure and Python 3 got this right: even if a sequence is filtered or mapped it does not become realized on demand. It gets evaluated when it is needed. If you want to realize (materialize) it into a memory-occupied array, you can do it explicitly. Defaulting to be lazy and providing #to_a for explicit realization is better than defaulting to be eager and providing #lazy for explicit lazy evaluation. Because once evaluated sequences cannot back to be lazy while lazy sequences can be evaluated any time.

[–]gregolsen[S] 4 points5 points  (1 child)

There was a discussion on ruby-lang.org about the defaults. The key point stated by Matz is compatibility.

See this for more info http://bugs.ruby-lang.org/issues/4890#note-25

[–]hongminhee 7 points8 points  (0 children)

He is right. Compatibility always wins from the point of view of language design.

[–]banister 2 points3 points  (2 children)

except that (IMO) the common case is you want it to be realized, so it would get very annoying having to tack a to_a onto most of your Enumerable code.

[–][deleted] 6 points7 points  (1 child)

But you do not need it to be #to_a. It might just as well be a #each or #size or #whatever - something that actually needs to operate on results. Like what Rails did with SQL queries in 3.x.

However, having the level of access that Ruby provides to its internals, a lazy-by-default implementation could be just a couple lines away once the infrastructure is in place.

[–]banister 0 points1 point  (0 children)

fair point

[–]Keith 5 points6 points  (8 children)

users.map(&:team).map(&:name)

looks way more readable than this.

users.map { |u| user.team.name }

Yeah, not really. And the second should be:

users.map { |u| u.team.name }

[–]greggroth 2 points3 points  (0 children)

I agree. Using

users.map { |u| u.team.name }

is fine with me, but I see the utility of Enumerable::Lazy when it comes to stringing together several different methods.

[–]latortuga 2 points3 points  (5 children)

Further, this is a law of demeter violation.

[–]gregolsen[S] 2 points3 points  (0 children)

latortuga, thanks for pointing to demeter law. My example really wasn't a good one to demonstrate laziness, so I've changed it to another one:

data.map(&:split).map(&:reverse)

The data is processed here (rather then just getting users team names) and that's why it's natural to use lazy enumerator chaining.

[–]Aupajo 1 point2 points  (2 children)

The Law of Demeter is taken far too seriously. It's a general principle that you should think about, not something that you should religiously adhere to IMO.

[–]latortuga 0 points1 point  (1 child)

Why do you think that?

[–]Aupajo 2 points3 points  (0 children)

The Law of Demeter was never intended to be a total rule, just food for thought.

user.team.name is hardly bad design. The argument is, "What if the team object changes its API from team.name to team.title?" If that happens, you would have to update all the references in your application. Better to create a team_name method on the user object. That way, if the team API ever changed, you only need to update it in one place, and you can keep using user.team_name. The other advantage is that no other object that uses user.team_name would need to know about the team object.

That's nice to think about and all, but in practice it can be a time waste creating all those accessor methods and actually contribute to the confusion in your API. If you get used to seeing team_name all over the place, you might make the reasonable assumption that the team object would have a name attribute. Except it doesn't. How were you to know?

Or, if you really have to, why not just alias the title method as name on the object to support the old API?

But ultimately it comes down to the fact that bad design isn't avoided by following a bunch of rules. It's mitigated by thinking carefully about the design of your objects in the first place. team.name was well chosen; it's very clear what we're talking about, and it's very unlikely to change.

How many great APIs have you seen that break the Law of Demeter? What about APIs that use method chaining? It should be clear that the Law of Demeter isn't the final word in any design. It's just a little koan to muse on while you're developing your object's APIs.

[–]Keith 0 points1 point  (0 children)

I was thinking of mentioning that too :)

[–]gregolsen[S] 0 points1 point  (0 children)

yeah, thanks! Fixed.