Live data from Hacker News

Some Smalltalk about Ruby Loops

tech.stonecharioteer.com

51–59 of 59 posts

Re: Some Smalltalk about Ruby Loops

#51
post #43

Earlier quoted context omitted.

You can iterate over strings and lists and arrays at the same time. You can do destructuring. You can accumulate values in every part of the loop, so if you have subloops you can accumulate values in the outer and inner and every intermediate loop and most important: it builds no intermediate results meaning it will be much faster.

Yes, you can do all that in ruby, plus you can choose to do it lazily such that chaining operations uses generators or by copy at any point so maybe this is a classic case of lispbian hubris.

Listen, my gripe isn't with ruby in particular. It is with every language out there.

Doing it that way is still going to be slower than doing it eagerly with a manual loop. I am not saying ruby should do away with the other ways of doing it. I am saying that ruby - and just about every language out there - should have a powerful looping facility that lets you do it fast without losing comfort.

If I - a non-programmer - could implement it using macros in scheme there is really no excuse for other languages to have such sucky foreach-like constructs.

For the vast majority of situations my goof-loop has no performance penalty compared to rolling the loop yourself. That is what none of the ruby examples I have been given can do. Using zip is not an acceptable way of iterating over 2 collections at once, at least not if you want to pretend to be efficient.

I don't think what I did is particularly good compared to something like the iterate macro, and all I want is for other languages to do better because I rarely have the pleasure of using scheme when doing coding.

Re: Some Smalltalk about Ruby Loops

#52
post #50
post #44

Earlier quoted context omitted.

Your example of the flatten and grep will build two intermediate arrays before summing. That's inefficient. I would also reach for higher order functions for most things, but when you need things to be fast that is not how you would do things. You would express it with nested for loops and ifs, and you would have to juggle state all by yourself. That is my main critique of looping constructs: they only cover the abso…

For the record, if you want to avoid the creation of intermediate arrays, you can: lst.lazy.grep(Array).flat_map(&:itself).sum Not as clear, because the standard library doesn't have a `#flatten` method for lazy enumerators. But the point is the interface, not the implementation. Efficiency doesn't mandate assembly-like DSLs. Your interface could be as clear and clean as Ruby's and produce fast, inlined code by the p…

I dont think interfaces is the point. As it is now in ruby you have to pick between comfort and speed when you can have both.

Continuation safety has never been a thing in ruby, so caring about it doesn't make much sense regardless of the presence of call/cc.

And lastly, neither my loops nor others I have mentioned only accumulate into lists. My loops a re in the most general sense a left fold, although right folds are possible to write as well. I provide accumulators for just about any data type available, and writing a new one yourself is pretty easy. You can express lazy loops with it as well.

All without sacrificing performance. Any performance in most cases. I haven't written much ruby since the 1.9 days, but back then I remember having to rewrite the nice .each{}.blah.blah into for loops. I could prototype in a nice way which was nice, but in the code I ended up shipping I had to juggle state using for loops because the thing I prototyped ended up being a bit slow.

I use map, filter and friends in scheme all the time, but when those are insufficient or too slow I don't have to resort to named lets. I can just use the loop macro which compiles to a named let.

Re: Some Smalltalk about Ruby Loops

#53
post #46
post #22

Earlier quoted context omitted.

A robust static type system is what was missing for me, at least last time I looked.

Static typing and messaging (OOP) don’t exactly fit together. If you want OOP, you are fundamentally beholden to dynamic typing.

I don’t want OOP. I just want the language to include a system for imposing constraints that prevent entire categories of bugs and make it easier to safely do large scale refactoring.

It’s really hard to go back to living without this once you’re used to it.

Re: Some Smalltalk about Ruby Loops

#54
post #51

Earlier quoted context omitted.

Yes, you can do all that in ruby, plus you can choose to do it lazily such that chaining operations uses generators or by copy at any point so maybe this is a classic case of lispbian hubris.

Listen, my gripe isn't with ruby in particular. It is with every language out there. Doing it that way is still going to be slower than doing it eagerly with a manual loop. I am not saying ruby should do away with the other ways of doing it. I am saying that ruby - and just about every language out there - should have a powerful looping facility that lets you do it fast without losing comfort. If I - a non-programmer…

I see the implementation of lazy zip here. But maybe by ruby you mean some other language.

https://ruby-doc.org/core-2.7.0/Enumerator/Lazy.html

Point is simply: No language is special or exceptional. If a language has yield, you can invert to your hearth's content. Even with lambdas/blocks you can invert by calling it in your loop. Speed is left to the efficiency of your jit or typed compilation. For example SBCL is able to compile incrementally by specifying types.

Re: Some Smalltalk about Ruby Loops

#55
post #51

Earlier quoted context omitted.

Listen, my gripe isn't with ruby in particular. It is with every language out there. Doing it that way is still going to be slower than doing it eagerly with a manual loop. I am not saying ruby should do away with the other ways of doing it. I am saying that ruby - and just about every language out there - should have a powerful looping facility that lets you do it fast without losing comfort. If I - a non-programmer…

I see the implementation of lazy zip here. But maybe by ruby you mean some other language. https://ruby-doc.org/core-2.7.0/Enumerator/Lazy.html Point is simply: No language is special or exceptional. If a language has yield, you can invert to your hearth's content. Even with lambdas/blocks you can invert by calling it in your loop. Speed is left to the efficiency of your jit or typed compilation. For example SBCL is…

Laziness is not free. An eager loop will be faster than a lazy construct, especially languages where the laziness isn't really a first class construct. Like ruby.

Rewriting the code you wrote (or was it the other guy? I am making food so I cant really check) to avoid intermediate collections as a for loop will be faster, especially if you can avoid creating those array pairs.

I am not saying ruby stinks. I am saying it - and just about every other language - is making it unnecessarily hard to write fast code.

Re: Some Smalltalk about Ruby Loops

#56

I like this article a lot but if you want to dig deeper and understand some of the negative consequences of Ruby's approach, you might like these two articles I wrote: https://journal.stuffwithstuff.com/2013/01/13/iteration-insi... https://journal.stuffwithstuff.com/2013/02/24/iteration-insi...

Wait I just realized you're Bob Nystrom. Weee.

Re: Some Smalltalk about Ruby Loops

#57
post #53
post #46

Earlier quoted context omitted.

Static typing and messaging (OOP) don’t exactly fit together. If you want OOP, you are fundamentally beholden to dynamic typing.

I don’t want OOP. I just want the language to include a system for imposing constraints that prevent entire categories of bugs and make it easier to safely do large scale refactoring. It’s really hard to go back to living without this once you’re used to it.

Coq, Idris, et. al. are over there if that's what you really want, but there is probably good reason why no "real world" programs are written in languages with proper type systems.

For one, there's little ability to avoid message passing in our modern world. You can take it out of the language, but that just means pushing it to another abstraction (e.g. sockets), and all the same lack of type safety comes right back.

Re: Some Smalltalk about Ruby Loops

#58
post #51

Earlier quoted context omitted.

Listen, my gripe isn't with ruby in particular. It is with every language out there. Doing it that way is still going to be slower than doing it eagerly with a manual loop. I am not saying ruby should do away with the other ways of doing it. I am saying that ruby - and just about every language out there - should have a powerful looping facility that lets you do it fast without losing comfort. If I - a non-programmer…

I see the implementation of lazy zip here. But maybe by ruby you mean some other language. https://ruby-doc.org/core-2.7.0/Enumerator/Lazy.html Point is simply: No language is special or exceptional. If a language has yield, you can invert to your hearth's content. Even with lambdas/blocks you can invert by calling it in your loop. Speed is left to the efficiency of your jit or typed compilation. For example SBCL is…

Adding to this: using laziness compared to a while loop in cases where you will process the whole array is just pure overhead.

Heck, testing now on 3.4.4 a while loop is faster than using each. Summing a million numbers (to make the overhead of the different approaches as clear as possible) shows that a while loop is almost 2x faster.

This is what my complaint is about. Why is the fast path so painful to use in so many languages?

Re: Some Smalltalk about Ruby Loops

#59

Even something as basic as "if" is done with message passing and blocks in Smalltalk. There's a method named "ifTrue:ifFalse:" in Smalltalk (with each ":" expecting an argument, in this case, a block). You can also chain messages and make phrases: `anObject aMethod; anotherMethod; yourself.` The Ruby equivalent has repetition: `an_object.a_method; an_object.another_method; an_object` or requires a block: `an_object.t…

In Ruby, methods called mainly for side effects rather than return value conventionally return the object itself, and so can be chanined by: an_object.a_method.another_method but, sure, it's less elegant (or at least less terse) than Smalltalk for the specific case of chaining invocations of methods with meaningful return values which are called for their side effects while discarding the return values.

I guess it's OK if the convention is well respected. It's the case in the jQuery library for example.

But yes it's a bit terse in Ruby because the default value is the last statement so it's known and predictable only if you look at the code paths. I would feel more confortable doing this if the IDE checks that it's OK.

Post reply on HN