21 ms·
Ruby vs. Python comes down to the for loop
- xfalcox 5y ago> Ruby, however, inverts this. Ruby puts object-orientation as the foundation of the pyramid. I'm doing some work using Jupyter/Python and as a Rubyst on my day job this is definetly how I felt! Since data science has so many data manipulation, the move from objects controlling the iterarion from language reserved words doing it was sooo weird at first.
- behnamoh 5y agoKinda opinionated and biased against Python.
- softwaredoug 5y agoAuthor here. Interesting! I personally write almost Python exclusively and find Ruby to be awkward in many ways. I'm learning Ruby, so if that comes through, maybe cause I'm learning it and more excited about new concepts? :)
- ewalk153 5y agoI read it as the opposite of the GP. You've highlighted the idiomatic differences in the language design and how each lends itself to a different style of thinking. Neat.
- drcongo 5y agoSame here. I thought it was well balanced and interesting.
- emi2k01 5y agoMay I know what GP means? It's the first time I see it.
- necovek 5y ago"Grand Parent" to refer to a post two levels up, though sometimes it's used contextually to mean whatever-post-that-started-off-this-thread (vs. "OP" or "Original Post(er)" for the top-level post).
- rpmisms 5y agoKinda like Ruby :P
- stevev 5y agoIn the end it is about preferences. It’s going to be biased given the subject at hand. I do prefer ruby. It’s more object oriented. The language really focuses on this. Example, the plus sign symbol (+) can have Methods or even be a class or be anything. That’s Ruby.
- inanutshellus 5y agoIMHO the author successfully remained neutral. Every point about the way one language works was neither criticism nor compliment, merely observation, and was immediately followed by the other language's differentiating take on a particular feature.
- ARandomerDude 5y agoSo what? At a certain point in your career you should have opinions about how problems should be solved.
- revskill 5y agodef f(): s = "Me too." print(s) # Global scope s = "I love Geeksforgeeks" f() print(s) This one returns `I love Geeksforgeeks` Holy mogy, God bless someone doing codereview or inherit python code from some bad developers.
- tmerr 5y agoIt prints "Me Too." then "I love Geeksforgeeks". What is strange about this?
- sgc 5y agoI would expect it to print "me too", because I would expect the assignment in the function to change the global variable when it is called, not create another variable.
- djmetzle 5y agoOh no. oh no no. Do you have examples of languages without the usual local scope binding semantics? I think the example here is misleading and causing confusion.
- chaxor 5y agoI believe Wolfram Mathematica has this terrible global scoping by default. It's not exactly a language that programmers refer to often, as it's not really general purpose, but I had to do some work with MRI data in it and I hated it mostly due to the scoping. An "I spent a decent bit of time rewriting the entire lab's codebase in python" type of hate.
- Macha 5y agoJavascript and C. Neither are considered pinnacles of language design.
- kevinmgranger 5y ago
- gilbetron 5y agoSeems odd to not talk about Python generators in an article about this topic.
- softwaredoug 5y agoAuthor here - True! though generators are a specialization of iterators discussed in this article. I had considered it, but didn't want to get too bogged down.
- isaacimagine 5y agoAt a deeper level, this comes down to internal vs. external iteration, and, more generally, how languages weave together concurrent threads of execution: https://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ https://journal.stuffwithstuff.com/2013/01/13/iteration-insi...
- DFHippie 5y agoThe discussion of Ruby could use a mention of Enumerable. https://ruby-doc.org/core-3.0.2/Enumerable.html https://ruby-doc.org/core-3.0.2/Enumerable.html
- softwaredoug 5y agoAuthor here, wow that's great! I am still learning Ruby (hence the article to try to grok things).
- pelasaco 5y agoas suggestion, regarding the following code: Stuff.new().each do |item| puts item end In idiomatic ruby, we drop this parenthesis, so the code will look like Stuff.new.each ...
- hirundo 5y agoputs Stuff.new.map(&:to_s).join("\n")
- usefulcat 5y agoIf one is having to implement an each or map or select method in ruby, then I can definitely appreciate the differences you point out. Even though I've been using ruby a long time, it would frankly still be a bit of a pain to have to remember the details of how yield works. Because Enumerable provides so much functionality, I almost never need to implement methods like those. So what I really care about a lot more are the ergonomics of using each, map, select, reject, all?, any?, etc, which I use very frequently.
- softwaredoug 5y agoYes though the point is the same, Enumerable gives the objects methods (internal iteration), in Python you would use built-ins max or filter or a list comprehension on an iterator (external iteration).
- 5y ago
- epage 5y agoFor me, its the explicitness in Python that is killer. I can see a name and match that to an import and match that to a package. Grabbing a function pointer works exactly as I would expect. Stealing from Krister Stendahl's laws for religious discourse, Ruby's composable iteration is one area I have holy envy, particularly after I got used to it in Rust. I've used Python's generators many times just to have to switch to an explicit for loop. Things are generally better with a composable iteration model but occasionally I find myself switching to explicit loops in Rust. Unlike Ruby, Rust does pull-iteration like Python, though there are experiments with Ruby-style push-iteration [0] [1]. [0] https://github.com/AndWass/pushgen https://github.com/AndWass/pushgen [1] https://epage.github.io/blog/2021/07/pushgen-experiment/ https://epage.github.io/blog/2021/07/pushgen-experiment/
- qsort 5y agoI agree. I'm not the biggest Python fan myself and would not recommend it for large projects, but I use it a lot both at work and personally for scripting/small stuff. It feels dumb, but I like dumb for those use cases.
- rgoulter 5y ago> I can see a name and match that to an import and match that to a package. I like this too, but... I don't find dynamic languages to be very explicit. Given that Python is a dynamic language, I only going to know what I can do with (or what is true about) the arguments to some function if I have worked with the codebase previously (or have sufficiently good documentation, and unit test coverage).
- horsawlarway 5y agoAt least with an explicit import you have something to search for. I have often spent several minutes trying to find a definition for a ruby method/modules/class only to finally have to run it and dump the file location manually with `source_location` to find out it's really a 3rd party gem added to the project. Admittedly, Rails makes this much worse.
- errcorrectcode 5y agoI use Crystal more these days because it looks like Ruby but it's typed and statically compiled.
- friedman23 5y agoCrystal does look interesting, it also supports parallelism with green threads right? The thing that always bothered me about ruby is that there are multiple ways to do the same thing in the standard library.
- yxhuvud 5y agoYes, it has green threads. Crystal don't have the same tradition of having aliases of common methods as Ruby does, no.
- derwiki 5y agoMore for professional or hobby projects?
- errcorrectcode 5y agoHobby mostly. Not very often. Use Rust mostly.
- lottospm 5y agoI was never a fan of ruby until I had to use it at work. I think my main superficial turn-off was indeed the non-traditional loops and I was a fan of python whitespace indentation. But on the whole ruby just feels very coherent and language features mesh very well together - it often feels like a true lisp with self-style object orientation done very well [1]. Python otoh feels like an imperative language with classical OOP added on and extended via magic methods. See e.g. functional programming in python vs ruby with map, filter, reduce and so on and how blocks and procs in ruby interact with those vs lambdas in python. I still use python often but have to say I tend to miss ruby when I do. [1] https://news.ycombinator.com/item?id=1803815 https://news.ycombinator.com/item?id=1803815
- pkulak 5y agoIt's always been the global functions in Python that put me off. My brain just doesn't think like that. When I want the length of an array, I look for a method on the array. Same with map, and anything else. That said... pretty small gripe in the grand scheme (no pun intended, maybe) of things. When I've written projects in Python, I've enjoyed it.
- echelon 5y ago> It's always been the global functions in Python that put me off. I'd argue it's the inconsistency. Some things are methods, some things are functions. The name choices are also a bit boneheaded (eg. "dumps"). It's a lot like PHP in these regards. Ruby, on the other hand, is beautifully designed. But it gives you a bazillion ways to skin a cat, and people love to be "clever" with their cat skinning. In those cases, I appreciate the utilitarian approach and "zen" of Python. My biggest gripe with Python, though, is the totally 80s feel of the version and package management. It's one of the worst experiences in my daily engineering life. I can never know if complex ML code will work on my system without thirty minutes of patching things up. I'll gladly use Python to get work done or Ruby for small websites. But I'm looking for a new daily driver for scripting that learns from the last thirty years of mistakes. (Modern language, sensible packaging/dependencies/version switch, optional static typing, and optional static binary output.) Nim, perhaps?
- fionic 5y agoCan ruby just die and go away already? But atleast it’s not another article stroking Rust’s shaft…
- stevev 5y agoDeveloper happiness, I believe, was one of the main pinnacles and design choice of Ruby according to Matz. Meaning he wanted developers to enjoy coding in Ruby.
- gilbetron 5y agoI think there are just multiple kinds of developers. I've programmed a fair amount, professionally, in Smalltalk and Ruby, and quite a bit in Python (along with lots of other languages). If I squint I can see why some kinds of developers like Smalltalk and Ruby more, but I am not that kind of developer. Python is better (for me) for small to medium projects, and then I prefer strict typing for larger projects (currently Go, which I'm ok with). But I know plenty of people that love, love, love Ruby and I respect them as developers, but what we enjoy in a language is just different.
- TheRealDunkirk 5y ago> I think there are just multiple kinds of developers. I've seen this very glaringly. I love Ruby and Rails, and want to leverage the stack as much as is reasonable. I've come to realize that there are people who really would like to write 100-200 lines of Java for every line of Ruby I write, just so that they "have control." I think they have no idea what they're talking about, but I have no influence over them. The fact that I'm writing my 3rd application in 6 years which does something that entire teams of outsourced developers could not do is, surely, just coincidence.
- joelbluminator 5y agoYes this is mostly it. Some of us just have a different world experience and are wired differently; the thing with engineers is that they don't like subjectivity - they need absolute answers, but it's really like debating who is better Metallica or Nirvana. The type of programmer who likes Ruby usually appreciates aesthetic code, freedom and expressiveness. The type of programmer that likes Go usually appreciates strictness, one way of doing things and performance. In the end to the average SAAS startup it really doesn't matter which of the popular languages you choose, it's been proven countless times that any of these languages can build monster size companies
- stefanchrobot 5y agoWhy not both? I want the language to provide an iterable/enumerable interface with the usual generic workhorses (map, reduce, etc.) and allow me to implement that interface as a free function for any type. If I'm authoring a type, I'll add specialized functions as needed.
- aardvark179 5y agoRuby manages to do this. The default implementations of all the methods you want are on the enumerable module, so if you implement the “each” method you get everything, including the ability to get an enumerator object that you can pass round and use for anything else you want.
- dvdhnt 5y agoI'll be absolutely subjective here. Python seems fine, except that I can't get comfortable in a language that uses whitespace as part of its syntax. I know the typical arguments of "linter, etc" but I believe my ruby and JS code, which includes using a linter and formatter, is easy to reason because brackets allow for better mental encapsulation. All personal experience, I can't speak to the technical differences.
- drcongo 5y agoI think once you get more used to it you'll find the complete opposite. All those brackets are extra things your brain needs to process when reading the code, strip them away and it becomes just like reading written language. Ruby to my eyes, even when well formatted, looks as ugly as PHP - a mess of unnecessary indications that destroy the readability of the code (for me).
- dragonwriter 5y ago> All those brackets are extra things your brain needs to process when reading the code, strip them away and it becomes just like reading written language No, it doesn't, because written language explicitly marks ends, and sometimes also begginings, of structural units (programming gets brackets from natural language), it doesn't just use white space (until you get to units bigger than sentences.)
- Thrymr 5y ago> No, it doesn't, because written language explicitly marks ends, and sometimes also begginings, of structural units (programming gets brackets from natural language), it doesn't just use white space (until you get to units bigger than sentences.) Written language does of course use white space to separate words (this was not always the case, see for example classical Latin). And I think it's fair to say that a line of Python is roughly equivalent to a sentence, and whitespace indentation is only used for syntax at that level. The analogy breaks down a bit at higher levels, as Python blocks can recurse, but in writing we have structures like chapters, books, etc.
- edmcnulty101 5y agoThis is a great article. I really enjoy reading articles comparing two languages and these kinds of posts are not as easy to come by as discussions about a single language.
- petercooper 5y agoI'm a longtime Rubyist who's dabbling in Python a fair bit nowadays due to the growth of the ecosystem and the libraries available. I find Python enjoyable to work with but the thing it makes me love more about Ruby is how in most cases you think about objects and the capabilities of those objects, whereas in Python it feels like a crapshoot (I'm sure it's not, it's just how it feels as a dabbler) as to whether you pass objects into a function (e.g. `len`), use methods on objects, or whether to even think of OOP at all :-) Now, that's not entirely a negative, I quite like Python's freewheeling "do whatever works and feels best for you" ways, but that feels somewhat contrary to the Zen of Python's "There should be one-- and preferably only one --obvious way to do it." (!)
- vikingerik 5y agoThose last aren't mutually exclusive. The idea is that there should be one obvious way, such that you'll find that what works and feels best is that way. The idea is that you will simply want to comply, not to demand that you do.
- mixmastamyk 5y agohttps://news.ycombinator.com/item?id=29202044 https://news.ycombinator.com/item?id=29202044
- bkovacev 5y agoI know it's not completely related to the thread - but how is Rails vs Django in terms of performance? I'm getting subjective information that Rails is much faster for APIs in terms of response times, serialization speed etc. although I have been using django for the past 7-8 years and have to say that the best response times was 200-300ms w/o caching for some heavy json responses. And something that should be blazing <100ms, still takes 200ms to respond.
- trimbo 5y agoThere are too many factors to make a broad comparison in milliseconds. Are you running the code on a RaspPi or on the latest Zen3? Serialization should be implemented in C so unless the JSON is megabytes in size, whatever is happening here probably has to do with the business logic implemented in Python. Python and Ruby are equally slow when they aren't wrapping frameworks primarily written in C (e.g. tensorflow), so choosing between them is IMO a stylistic or compatibility choice.
- zitterbewegung 5y agoIn my opinion it doesn’t matter which language you use as long as it’s the right tool for the job . Usually this goes for familiarity of either who you can hire and or you. Python has some obvious syntax weirdness and Ruby looks like it encourages more DSL and that might make it closer to lisp. But, Python has so many more libraries and a good editor can help you manage the white space indentation . Also Python scales well too. To be honest I rather have some kind of different framing of this. Instead of an argument it could have been a comparison but that doesn’t get people to click the link.
- FpUser 5y ago>In my opinion it doesn’t matter which language you use as long as it’s the right tool for the job" And "the right tool for the job" is of course language one knows and loves ;)
- mgraczyk 5y agoHmmm, I don't think this article accurately represents how people write python since ~2005. I'm biased because I use python every day, but there's a big objective mistake. > Ruby flips the script, giving the objects deeper customizability. Not really, because python allows you to do things the Ruby way, but I'm not sure the reverse is true. class Stuff: def __init__(self): self.a = [1, 2, 3, 4] def __iter__(self): for item in self.a: yield item and you can write it more simply in this case. def Stuff(): a = [1, 2, 3, 4] for item in a: yield item What about the select and map example? class Stuff: def __init__(self): self.a = [1, 2, 3, 4] def __iter__(self): for item in self.a: yield item def map(self, f): for item in self.a: yield f(item) Which can be used just like the ruby example, with syntax of similar ugliness. print(Stuff().map(lambda item: item)) I think I could come up with a python example that maps 1:1 onto pretty much any ruby example, but I don't think it's possible in general to find ruby examples that map onto more complicated python.
- softwaredoug 5y agoAuthor here! Very true, though I was trying to focus on idiomatic python that pushes logic into for loops, list comprehensions, etc w/ composable iterators through tools like those found in itertools (zip, etc...) Since Python doesn't have Ruby's blocks, you have to define a non-trivial function as a free function, so it's less likely you'll do this. (Python's lambda's are far more limited than Ruby's blocks.) You can also trick Ruby to return a new value every time a method is called for iteration, but again my focus on what I see as idiomatic in the two languages
- gvx 5y agoYour Python example is far from idiomatic, though. If a Python programmer were forced to write `Stuff`, they'd write something like: class Stuff: def __init__(self): self.a_list = [1,2,3,4] def __iter__(self): return iter(self.a_list) --- or do what the other person did, and make `__iter__` a generator, which isn't necessary in this case but is much more flexible.
- tomp 5y agoAlso known as "external" vs "internal" iterator. Bottom line: external iteration is composable, internal iteration isn't. Try implementing zip or early stopping using internal iteration (hint: you can't).
- chubot 5y agoYup this is a great article about it that I coincidentally re-read yesterday: https://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ https://journal.stuffwithstuff.com/2013/01/13/iteration-insi... I find it easier to think of as "push-pull" though. In Python and Go, you "pull" from iterators. In Ruby, JavaScript, and Rust, you're "pushed" upon. You can do both styles in any language, but there's one way that's favored. To break the dilemma, you need 2 call stacks, which means threads / coroutines / generators. (There was a recent blog post about writing an iterator to do the powerset calculation, which is a bit tricky, and made me think of this post)
- gpderetta 5y agoYou can if your language supports first class continuations! (Which ruby has: yield work differently from python I think). Then you can turn any internal iterator into an external one.
- racl101 5y agoAs a guy who uses a lot of languages and having started on C-like languages I appreciate Python in that it doesn't make me work terribly hard to get what I need to get done. I'm far from a Python expert cause I don't use it exclusively. In spite of all that, it's pretty intuitive, reads like pseudo code. Plenty of data types and packages to get the job done. The few times that I've touched Ruby, however, even though it's succinct and beautiful language, I always have to find myself relearning its super object oriented centric paradigm. Everything takes 2-3 times as long to achieve and, arguably, not that fast a language either. I suppose if i worked on web app project exclusively, maybe Ruby might be my language of choice, but I don't. I'm sort of a jack of all trades master of none position (but that's ok cause in my particular situation I get paid pretty well for it). So that's why I prefer Python at the moment.
- StefanKarpinski 5y agoThis feels a bit ironic given how much the for loop has been villainized in numerical computing, by none other than languages like Python—and Matlab and R—where for loops are so awfully slow that you can only get performance if you avoid them like the plague and write (sometimes awkward) vectorized code that pushes all the for loops down into some C library (or C++ or Fortran). Compared with, say, Julia, where people are encouraged to "just write yourself a dang for loop"—because it's not only effective and simple, but also fast. I guess what I'm saying is that it feels like even though Python may embrace the for loop as syntax, that affinity seems superficial at best, since if you care at all about performance then Python immediately rejects the for loop.
- anonpython 5y agoIn Python numeric computing it's common for your outer loops to be for loops and your inner loops to be vectorized PyTorch/whatever. I personally like being able to easily comprehend and control what's being vectorized. Maybe it would be nice if my compiler could automatically replace any inefficient loops with vectorized equivalents, and I could think in whichever idiom came more naturally to the problem at hand. But I don't think there's anything too illogical about looping over epochs and batches, and then computing your loss function with matrices. Maybe I'm just used to a suboptimal way of doing things :)
- StefanKarpinski 5y ago> Maybe it would be nice if my compiler could automatically replace any inefficient loops with vectorized equivalents The trouble is that a for loop is much more expressive than vectorized operations, so most for loops cannot be transformed into vectorized equivalents. The reason convincing people to write vectorized code works for performance is that you're constraining what they can express to a small set of operations that you already have fast code for (written in C). Instead of relying on compiler cleverness, this approach relies on human cleverness to express complex computations with that restricted set of vectorized primitives. Which is why it can feel like such a puzzle to write vectorized code that does what you want—because it is! So even if a compiler could spot some simple patterns and vectorize them for you, it would be incredibly brittle in the sense that as soon as you change the code just a little, it would immediately fall off a massive performance cliff. I guess that's actually the second problem—the first problem is that there isn't any compiler in CPython to do this for you.
- qwertox 5y agoI have nothing to contribute to the content of this article, but I really want to express my gratitude to the author. I've been using Python for over a decade and installed Ruby once or twice just to touch it, and I really like how this article has managed to bring Ruby onto my radar, not as something which I should use, but should appreciate. For me it was just a Python alternative which some companies really do like, but this article told me a bit about the beauty of the language. Nice differences.
- softwaredoug 5y agoThanks! I appreciate the kind words!
- dorianmariefr 5y agoThis is not idiomatic ruby, you would do class Stuff attr_reader :list def initialize @list = [1, 2, 3, 4] end end Then: Stuff.new.list.each { |item| ... } Stuff.new.list.map { |item| ... } Stuff.new.list.select { |item| ... } And if you want top-level each/map/select methods, you could do delegate :each, :map, :select, to: :list
- bwilliams 5y agoIt's also worth mentioning the Enumerable module that can be mixed in to classes, which gives you `map`, `select`, and much more for free by implementing `each`. https://ruby-doc.org/core-3.0.2/Enumerable.html https://ruby-doc.org/core-3.0.2/Enumerable.html
- softwaredoug 5y agoAuthor here, nice! TIL :)
- stouset 5y agoI have written Ruby professionally for like fifteen years and I have not once witnessed a `for` loop. I’m genuinely confused as to where you picked this up. Enumerator and Enumerable are endemic.
- vidarh 5y ago~15 years here too, and I can't remember my last for loop either. I think for loops are mostly used in Ruby by people that picked it up in other languages first and simply never unlearned it.
- halostatue 5y agoAlmost twenty years here. I have definitely seen and used `for` loops, but they are the _exception_ and usually used in one-off scripts.
- xtracto 5y agoI would actually say that if I found a for loop in Ruby, it will be a code smell.
- lern_too_spel 5y agoThe very first code block has a fundamental error. It allows only one iterator per Stuff instance.
- Mikeb85 5y agoRuby is the first language I learned (apart from dabbling with Visual Basic as a kid). I still reach for it to actually get things done. One thing I always appreciated about it was the consistency of everything being an object and how that allows you to just chain methods together. Kind of reminds me of Lisp in it's consistency (although in Lisp everything is data or a list, not an object). Later I dabbled in Smalltalk (Pharo) just for fun to try to understand what inspired Ruby's object model. I've tried Python, hated the inconsistency of it, but then again I never tried any C family language before learning Ruby. Calling Each then feeding it a block feels more natural and consistent than a for loop IMO, even if for loops are the standard 'programming' way of doing it. It does make sense to use it as an analogy to describe the differences between the languages.
- peterthehacker 5y ago> I've tried Python, hated the inconsistency of it Can you elaborate on this? I’ve written in both Python and Ruby, but I’m not sure what you mean by this critique of python. I wouldn’t characterize either language as “inconsistent.”
- chucke 5y agoa[0]. a is an array. If the array is empty, what's the result, in both languages?
- Mikeb85 5y agoIn Python some standard operations are global functions and some are methods. Which are which isn't exactly clear. It's also kind of irrational and random but I hate seeing __init__ everywhere. For example, to find the length of an array in Python you use the function len(). In Ruby you call the method .length. As the article is about, loops in Python are for blah in blah. In Ruby they're blah.each and for loops are just sugar (not sure why they're there but you can avoid them). In Ruby pretty much everything is an object and you just call methods. Very few keywords, global functions, etc... Also, as the other poster said, if you call a[0] on an empty array, in Ruby you get nil, in Python it throws an error. Not sure which is better or considered more 'proper', but Ruby's behaviour is what I'd personally expect in a dynamic language.
- jrochkind1 5y agoThis was definitely written by a pythonist! If I tried to write it, as a rubyist, I'm sure I'd get some things about python wrong. (I find it notable how few people there are that are actually familiar with both). The standard alternative to `for` in ruby does involve `each` and blocks... but definitely doesn't involve defining a custom `each` method on your class... That is a specialty thing that most developers have probably done rarely. Let alone defining it in terms of `for`, which is just weird! But the basic principle stated "Instead of passing data back to the for loop (Python) you pass the code to the data (Ruby)" -- is more or less accurate. blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby. > Python builds on for-like constructs for all kinds of processing; Ruby pushes other kinds of data processing work to methods. OK, maybe, although not totally sure what you mean. > Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below: Um. I'm not sure where the author is getting this. It is certainly possible, as shown, but definitely not common to implement `select` or `map` or other methods provided by Enumerable directly on your custom class. It is a bit more common to implement `each` alone and let the `Enumerable` mixin provide the rest. But even that I'm not sure how "common" I'd call it. > Ruby, however, inverts this. Ruby puts object-orientation as the foundation of the pyramid. Ruby contains the messy procedural world in blocks, letting objects work with those procedural blocks. OK, true. The author is getting the details wrong, but I guess their overall picture is still right?
- dehrmann 5y agoInterestingly, Java has both styles. You can do for (var i : list) {...} or list.forEach(i -> ...); Except for cases with local variables, I have absolutely no idea which I should prefer. Kotlin makes a cute case for the functional style with it's lambda shorthand: list.forEach { ... } I've found the functional style helpful for times I want almost add a language feature to avoid boilerplate. The functional style was added without special-purpose language changes, but the iterator version required Java to add Collections, Iterators, and eventually fancy for-each syntactic sugar.
- entropicdrifter 5y agoThe big advantage of the functional style in Java is that you are disallowed at the compiler level from modifying the contents of the collection you're iterating over. That sort of compiler-enforced good practice is definitely for the best.
- dehrmann 5y ago> you are disallowed at the compiler level from modifying the contents of the collection you're iterating over This fails at runtime, not compile time for me: x.forEach(i -> x.add("a"));
- mumblemumble 5y agoYou could dig even deeper. The reason for these differences in how the for loop works happen because Python comes from the procedural tradition, while Ruby comes from the Smalltalk tradition. In other Smalltalk-inspired languages (but not Ruby), you can do the same thing with an even simpler language feature: if-statements. In procedural languages, including object-procedural languages such as Java and Python, an if-statement is a core language feature with its own syntax. In Smalltalk, conditions are kicked out of the core language and into the standard library. You have methods on the True and False objects that either take an anonymous function and execute it or perform a noop, or take two anonymous functions and decide which one to execute.
- dragonwriter 5y agoWhile people have pointed out that this is not idiomatic: class Stuff def initialize @a_list = [1, 2, 3, 4] end def each for item in @a_list yield item end end end I haven't seen anyone point out the deepest reason why: unlike in many languages, “for” is not a basic, low-level construct in Ruby, it is syntax sugar. That is: for x in y x.stuff more_stuff end is sugar for: y.each do |x| x.stuff more_stuff end (Also, for quite a long time, for...in also had runtime overhead , so it was slower syntactic sugar.) for...in is rarely used (it is syntactic sugar that is neither more concise bor more specific in intent, but more familiar to people with experience in non-Ruby languages), and mostly on scripts that consume but don’t implement collections. If you are getting deep enough to be implementing an #each method for a custom collection, that's an odd place to use for.
- saghm 5y agoI'm not sure if this was ever changed, but I remember running into something surprising regarding variable scoping with for loops (I think it was during an internship in 2014, so I think Ruby 2.x with some relatively low value for "x"). Unfortunately I can't remember exactly what it was; I think it was something about variables defined in the for loop still being in scope afterwards? That doesn't seem like it should be the case if it's just syntactic sugar for `each` though, so I might be remembering wrong
- dragonwriter 5y agoYes, for loops (and similar statements) don't introduce scope, so its not quite simple sugar for .each, and there are a few cases where you might use them over each for that property (if you are mutating values that you want to access afterward, it can be more concise than each) but they are mostly anti-idiomatic.
- necovek 5y agoBut that actually makes it more similar to Python. In Python, `for value in iterator: x(value)` is syntactic sugar for (roughly) while True: try: value = next(iterator) except IterationError: break x(value) Basically, it seems that `while conditional` are both low-level control flow constructs in both: how do they differ between languages?
- deleted 5y ago[deleted]
- barefoot_coder 5y agoIf this was interesting to you I suggest reading https://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ https://journal.stuffwithstuff.com/2013/01/13/iteration-insi...
- gorgoiler 5y agoPrescient. I found myself wanting to pass a block of code to a Python class today, like what I used to do when I wrote Ruby. I very nearly hacked it up using cursors and an iterator but I saw sense before birthing a monster.
- antod 5y agoTo me, the biggest fundamental difference between Ruby and Python seems to be that: In Python methods are implemented via callable attributes (it's dicts all the way down) In Ruby attributes are implemented via methods (it's messages all the way down) I've been working professionally in Ruby for 5+ years, but non-professionally using/dabbling Python for 15 before that. Personally I still find Python more intuitive though even if I haven't used it for ages - it just fits my brain more automatically and quicker. Ruby is nice overall (ie the smalltalk inspired bits), but the more Perl inspired bits irk me, and parts of the Ruby community can produce some insane library/framework code trying to make interfaces as "elegant" as possible.
- toxik 5y agoAs an almost exclusive Python programmer, I’ve often looked at Ruby’s block syntax with jealous eyes. It would be so nice. But I wonder if that’s maybe the exact thing that I also like about Python: the language is pretty damn simple in its essence. You want to minimize cleverness wherever possible, and that idea is firmly planted in the community. Ruby has this sense that Ruby is almost a meta language, you create cool “DSLs” in it to accomplish your goal. That makes it harder to fit into the brain also.
- burlesona 5y agoFor what it’s worth, the language itself is extremely simple and composable, which is what makes it possible for people to use it as a kind of metalanguage. That was really popular in Rails gems around the time that Rails itself peaked in popularity, but has since died off considerably. It was never especially common outside of Rails gems. There aren’t that many things that call for DSLs.
- vidarh 5y agoDSLs remain very common in Ruby, but Rails taught us to avoid littering. DSLs now tend to be intentionally very tightly scoped. E.g. Rouge, the Ruby equivalent to the Pygments syntax highlighter, defines DSLs to define lexers and themes. The do so in terms of methods on a meta-class so that the DSLs are confined to the definition of a lexer or theme class inheriting from the right classes, so there's a single place to look at the definitions, and their use don't leak all over the place.
- nomoreusernames 5y agoare we still doing the whole y vs x thing with languages? why are people wasting time on this? assembler wins. we already know this. the rest is just laziness. /this is sarcasm.
- kazinator 5y agoTXR Lisp: $ txr stuff.tl 1 2 3 4 $ txr stuff-cons-like.tl 1 2 3 4 $ cat stuff.tl (defstruct stuff () (vec (vec 1 2 3 4)) (:method length (me) (len me.vec)) (:method lambda (me index) [me.vec index]) (:method set-lambda (me index val) (set [me.vec index] val))) (each ((x (new stuff))) (prinl x)) $ cat stuff-cons-like.tl (defstruct stuff () (list (list 1 2 3 4)) (:method nullify (me) me.list) (:method car (me) (car me.list)) (:method cdr (me) (cdr me.list))) (each ((x (new stuff))) (prinl x)) Quit with :quit or Ctrl-D on an empty line. Ctrl-X ? for cheatsheet. 1> (disassemble (compile-toplevel '(each ((x (new stuff))) (prinl x)))) ** expr-1:1: warning: new: stuff does not name a struct type data: 0: stuff syms: 0: struct-from-plist 1: iter-begin 2: iter-more 3: iter-step 4: iter-item 5: prinl code: ;; manually annotated! 0: 2001000A gcall t10 0 d0 ;; t10 <- (struct-from-plist 'stuff) 1: 04000000 2: 20010009 gcall t9 1 t10 ;; t9 <- (iter-begin t10) ; t9 is iterator 3: 000A0001 4: 20010006 gcall t6 2 t9 ;; t6 <- (iter-more t9) ; more items? 5: 00090002 6: 3800000F if t6 15 ;; if t6 not nil continue, else goto 15. 7: 00000006 8: 2001000A gcall t10 4 t9 ;; t10 <- (iter-item t9) ; get item 9: 00090004 10: 20010006 gcall t6 5 t10 ;; t6 <- (prinl t10) 11: 000A0005 12: 20010009 gcall t9 3 t9 ;; t9 <- (iter-step t9) ; step iter 13: 00090003 14: 34000004 jmp 4 ;; repeat 15: 10000000 end nil ;; terminate, yielding nil instruction count: 9 #<sys:vm-desc: 14fcb80>
- deleted 5y ago[deleted]
- dnautics 5y agoAfter spending years in functional-land, I find the for loop (and while loop) construct maddening; you have to maintain the state of the entire enclosing scope system in your head, whereas enumerable constructs like map and reduce close (ideally read only) over variables in scope and if you need to keep state you have to use reduce, which explicitly tracks what changes between iterations and nothing "unexpected"... In a general sense, FP enumerations are more explicit than control loops which are implicit state machines.
- peterthehacker 5y ago> After spending years in functional-land, I find the for loop (and while loop) construct maddening; you have to maintain the state of the entire enclosing scope system in your head. This is only true if immutability is enforced. In js you see map used to mutate variables outside the scope of the map closure all the time. const o = {} const d = [1, 2, 3, 4] d.map(i => o[i] = i**2) Which is equivalent to this python. o = {} d = [1, 2, 3, 4] for i in d: o[i] = i**2 The cognitive load is the same in both. The strength of pure FP languages come from enforced immutability, but that constraint often adds cognitive load in other ways.
- dnautics 5y ago> constraint often adds cognitive load in other ways In my experience (and others) those constraint only reduces cognitive load, it can increase actual performance load, and can make certain algorithms "basically impossible", but you're also never actually writing those algorithms. When was the last time you ACTUALLY used dykstra's A*? Come on, most of us are writing SAASes, APIs/backends and basic frontends here (yes, the rest of you do exist), and even for shitty O(n^2) algos, your n is probably in the 10-20 range. Your bad algorithm will not take down the server.
- toxik 5y agoPeople write exploding time complexity code in bad places all the time. I absolutely disagree with this sentiment that it won’t matter.
- mdavidn 5y agoThe author overlooked the most mind-bending feature of Ruby blocks, the thing that makes them so very different than lambdas in other languages: Blocks can return from the enclosing function. def process collection.each do |element| return element if condition end end This has the effect of popping three (or more) frames off the stack. From the perspective of #each, yield never returns.
- nixpulvis 5y agoI'll link this, because it's mostly related, why not: https://nixpulvis.com/ramblings/2020-03-16-set-builder-notation https://nixpulvis.com/ramblings/2020-03-16-set-builder-notat...
- burlesona 5y agoIf you want, you can write Ruby Enumerators (not Enumerables) as well, which work like the Python example. A fairly concise example: https://garycordero1690.medium.com/ruby-enumerator-74b84615f4e8 https://garycordero1690.medium.com/ruby-enumerator-74b84615f...
- vidarh 5y agoOf course writing an Enumerator is writing the equivalent to an "each" method, hence why you can also trivially turn any object that implements each into an Enumerator.
- taeric 5y agoIt is funny to add other languages to this discussion. To wit, I'm reminded of the article regarding someone learning clojure where their coworker left them a note that read "just use a hashmap!" Indeed, in so much of lisp, it is all just some form of list. Either lists that have a first and a rest, or those that are alternations of names and values, or those that are pairs of items, etc.
- jmalicki 5y agoI wish someone wrote this in 2005 when both were coming up as popular languages!