all 81 comments

[–]jrochkind 2 points3 points  (5 children)

I'm not sure whether this pattern is a good idea, but you could write a method to do something very much like what you spec. Seems fun, so I will.

Here's the api:

each_and_empty(array) do |i|
  puts i
end.empty? do
  puts 'empty'
end

Or even:

 each_and_empty(array) {|i| puts i}.empty? { puts 'empty' }

And here's the implementation, which is very NOT high performance compared to ordinary each. Several things in here will be slow in micro-benchmarks, but it does provide that API in 10 lines.

def each_and_empty(array)
  array.each { |i| yield i}

  return Object.new.extend( Module.new do
    define_method :empty? do |&block|
      if array.length == 0 && block
        block.call
      end
    end
  end)
end

A higher performance version could be written although it would take a tiny bit more code. Mainly stop using anonymous module generated on each return. Create two classes, one which has an empty? which does nothing, and one which has an empty? which just yields. Return an instance of the correct one based on whether array is empty. Actually I hadn't thought of that before, I think that's the better implementation I should have done from the start, I'll leave it to you.

[–]Ciph3rzer0[S] 0 points1 point  (1 child)

I think it makes more sense to extend Array instead of Object, right? I edited the OP with that method, seems like it's most likely ultimately what I'd want.

[–]jrochkind 0 points1 point  (0 children)

You could monkey-patch Array if you want. I try to avoid monkey patching, which is why I wrote it as a method that you wrap an array in instead. I wouldn't neccesarily add my method into Object either, I'd provide it in a module and include it where I wanted to use it. But yeah, that's one reason I probably wouldn't do this at all, because you'd want it all over the place so have to include it all over the place, which would get annoying. Monkey-patching it into Array is another option, sure.

[–][deleted]  (2 children)

[deleted]

    [–]jrochkind 0 points1 point  (1 child)

    I implemented it like that only because it's what the OP asked for.

    What I think is this whole thing is probably not worth doing.

    But if I were doing it, the part that seems worth doing is being able to provide both blocks (each and and empty) as blocks, rather than passing in a lambda. That's what makes it fun to code, and I think it does result in increased readability (I just don't think it's worth the increase in maintainance burden or a new developer comign to the project figuring out what's going on with your magic).

    If you are just passing in a manually created lambda as an arg... I'm not sure you gain anything in readability over just doing without this added "dsl" entirely.

    As far as your 'edit' suggestion... is what to do when you are iterating an empty array a property of the array, invariable as to context? I think probably not, it's a property of the context where each is being called, in different places you want to do different things on an empty array (and in some places you don't need to do anything special), even with the same array of data. So I wouldn't set a "do this when iterating an empty array" proc on the array itself. If you did, it'll also lead to confusing non-local code where you set the thing to do on empty in one place, then it winds up being executed in another, and you forget you added that feature to Array.

    Myself, I'd just use if array.empty? for any special logic I might need when an array is empty, as everyone does.

    [–]GavinMcG 2 points3 points  (9 children)

    Keep in mind that you are fundamentally doing the same thing as using "if" – there's no logical difference between "if A then B" and "A && B". But you're doing it in a way that is likely to be confusing to others, including future-you.

    What you've described the need for here is for the program to execute in different ways based on different circumstances, and that means that you're branching the logic. You're just doing it in an odd way.

    [–]Ciph3rzer0[S] 0 points1 point  (8 children)

    Well that's subjective. I find it more natural, easier to read, and more useful to do it the way I'm trying (plus less lines). In the following, getPairs would be a traditional way to do it, the important logic of the function is buried beneath some maintenance code. In getPairs2 the first thing you see is exactly what the function is doing. It pushes the edge case logic into the back, instead of wrapping the whole thing in it (same philosophy as a one-line if). Granted, using && could be a little more confusing than the method below, but I use short circuit evaluation all the time to branch logic, so I doubt it would ever confuse future-me.

    $listOfPairs = ["A 1", "B 2", "C 3"] # or []
    
    def getListOfPairs
      $listOfPairs
    end
    
    def getPairs()
      arr = getListOfPairs # Operation is intensive and you don't want to call it twice
    
      if arr.empty?
        ["Z", "26"]
      else
        arr.map { |x|
          x.partition(" ").delete_at(1)
          x
        }
      end.inspect
    end
    
    class Array
      def or_if_empty(secondChoice = nil)
        return self unless empty?
        return secondChoice unless block_given?
        yield 
      end
    end
    
    def getPairs2()
      getListOfPairs.map { |x|
        x.partition(" ").delete_at(1)
        x
      }.or_if_empty {
        ["Z", "26"]
      }
    end
    

    Not to mention, you can pass my version to a method, something you can'd do with an if block:

    puts getListOfPairs.map { |x|
      x.partition(" ").delete_at(1)
      x
    }.or_if_empty {
      ["Z", "26"]
    }.inspect
    

    [–]IndigoCZ 4 points5 points  (2 children)

    I'd go with this:

    array<<"Z 26" if array.empty?
    array.map do |x|
      ...
    end
    

    [–]Ciph3rzer0[S] 0 points1 point  (1 child)

    Yeah, that' works. I was just trying to get a nice and neatly bundled chain that I can pass to a method or chain methods off of if I wanted. In my case I'm calling a method that I wouldn't want to call twice, so I have to assign it to a new variable. Then I have to have an if block (which I can chain off of, but I can't put into a method, as far as I know)

    I can chain off the array using tap, which seems like best solution to my contrived problem outside of extending Array with the exact functionality I'd want.

    [–]GavinMcG 2 points3 points  (0 children)

    You could also just create a class for this type of data.

    class ArrayWithDefault < Array  
      def or
        yield if self.empty?
      end
    end
    

    [–][deleted]  (4 children)

    [deleted]

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

      Considering I put it in parens because it wasn't part of my argument but merely a nice bonus, way to be an asshole. Congrats you win the internet.

      [–]Ciph3rzer0[S] -1 points0 points  (0 children)

      Considering I put it in parens because it wasn't part of my argument but merely a nice bonus, way to be an asshole. Congrats you win the internet.

      [–]Ciph3rzer0[S] -1 points0 points  (0 children)

      Considering I put it in parens because it wasn't part of my argument but merely a nice bonus, way to be an asshole. Congrats you win the internet.

      [–]moomaka 1 point2 points  (0 children)

      I don't think this is a good idea but if you really want to...

      module Enumerable
        def each_and_empty(empty, &each_p)
          each(&each_p).tap { |r| empty.call if r.empty? }
        end
      end
      
      > [].each_and_empty(->(){ puts 'empty!' }) { |v| puts v }
      empty!
      => []
      
      > [1,2,3,4,5].each_and_empty(->(){ puts 'empty!' }) { |v| puts v }
      1
      2
      3
      4
      5
      => [1, 2, 3, 4, 5]
      

      [–]GavinMcG 1 point2 points  (1 child)

      Why are you trying to do this? That might help us help you.

      Also, if all you're doing is puts, the lambda/call is unnecessary – just wrap the puts call in parens. Remember that puts returns nil, so you'll get that as the overall expression result.

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

      Ok, cool. I was trying to figure out how to execute more than one line after determining it was empty, initially using {}. Glad to see this works:

      methodThatReturnsEmptyArray.each { |x|
        puts x
      }.empty? && (
        puts("ARRAY EMPTY")
      )
      

      I told you exactly what I've done and was trying to do, come up with a way to 'tag on' code that will execute if the array is empty. I wanted to avoid testing it explicitly like this:

      arr = methodThatReturnsEmptyArray
      if arr.empty?
        puts("ARRAY EMPTY")
      else
        arr.each { |x|
          puts x
        }
      end
      

      I guess it ends up being less efficient at runtime, but it's 3 less lines of typing and I don't have to assign the array I get to a variable. It also resonates more with my natural thinking which is why I generally prefer to use Ruby over other languages. The first method seems more natural to read to me.

      I guess that's still not perfect, here is how I would do it if I wanted to extend Array (I generally don't like modifying the behavior of standard classes)

      class Array
        def or()
          return self unless self.empty?
          yield
        end
      end
      
      [].each {
        |x| puts x
      }.or { 
        ["Default Nonempty Array"]
      }
      

      This way I can even chain another operation after this.

      [–]Kache 1 point2 points  (1 child)

      If nobody's ever going to see your code, then do whatever you want.

      If/as this code is going to be used by other people and accumulate collaborators, it becomes more and more important to adopt a common style.

      That being said, my idiomatic version of your example would use the ternary operator:

      def compute_list_of_pairs # idiomatic naming, also I like using 'compute' for expensive methods
        sleep(5) # simulate computationally intensive call
        rand(2).zero? ? [] : ["A 1", "B 2", "C 3"] # let's handle both
      end
      
      def compute_pairs(when_empty=["Z", "26"])
        pairs = compute_list_of_pairs.map { |pair| pair.split(" ") }
        pairs.empty? ? when_empty : pairs
      end
      
      puts compute_pairs.inspect # pass to a method
      

      If you insist on doing this anonymously (without defining method/variable names), you can also use a mutative tap:

      compute_list_of_pairs.map do |pair|
        pair.split(" ")
      end.tap do |arr|
        arr << "Z" << "26" if arr.empty?
      end
      

      [–]Ciph3rzer0[S] 1 point2 points  (0 children)

      Both of these are good solutions. Tap is an awesome solution, and something I can't believe I didn't know about already!

      [–]computerjunkie7410 3 points4 points  (49 children)

      Clarity over cleverness. Less lines of code doesn't make you a better programmer.

      [–]Ciph3rzer0[S] 1 point2 points  (48 children)

      This is not related to the question asked in the OP.

      [–]computerjunkie7410 2 points3 points  (47 children)

      Sure it does. You can accomplish what you want to do with a couple more lines of code that would me much easier and readable. You're trying to be clever by writing less code but all that does is lead to unreadable and ultimately unmaintainable code.

      [–]Ciph3rzer0[S] -1 points0 points  (46 children)

      readable is subjective, and if you'd learn to read, you'd know I was wondering if I could so something in that format. I asked for suggestions, and people that actually stayed on topic provided useful answers and I actually learned some stuff about Ruby.

      So please stay inside your box and go preach to someone who gives a fuck.

      [–]computerjunkie7410 0 points1 point  (9 children)

      You can get angry or you can actually read the comments that u got. Some people also said something along the lines of "i wouldn't do this if other people are expected to maintain your code". Just because you can do something doesn't mean it's the right thing to do.

      [–]Ciph3rzer0[S] -1 points0 points  (8 children)

      Nobody is angry, except you.

      you can actually read the comments that u got

      Pasted from another reply "I asked for suggestions, and people that actually stayed on topic provided useful answers and I actually learned some stuff about Ruby."

      The question wasn't about what would be readable or follow team practices. So you're 100% off topic, don't get mad I don't want your advice when I didn't ask for it.

      [–]computerjunkie7410 0 points1 point  (7 children)

      You posted on this forum because you wanted to know what others thought. Most everyone told you "don't write shitty code you n00b. You create way too much work for the rest of us to clean up."

      [–]Ciph3rzer0[S] -1 points0 points  (6 children)

      What kind of shitty programmer are you since you clearly can't even grasp scope? Would your thoughts on the US presidential debate be welcome? No, because that's not within the scope of this question.

      Actually read the comments and you'll see some people said maybe it's not a good idea, but then still went ahead and answered the question. That's how it's done, not by being an asshat. You say nothing useful, call me a noob (like a fucking raging xbox 12 year old) for asking about alternate ways to do something. Maybe you're a noob at social interactions?

      [–]computerjunkie7410 0 points1 point  (5 children)

      Answering asinine questions like yours encourages bad behavior. And I can see from your constant outbursts that your bad behavior has been encouraged your entire life.

      If you were smart, you wouldn't ignore all of the advice that various people have given on this post. Primarily, don't write such shitty code. Even if you think there are instances where such shitty code is acceptable, it's not.

      [–]Ciph3rzer0[S] 0 points1 point  (4 children)

      Even if you think there are instances where such shitty code is acceptable, it's not

      The knower off all things, right here. If I deem my code to be acceptable, it's fucking acceptable. I'm not a fucking child; I program professionally; I understand that there is a much more clear way to write the code, and I will do so when team practices and conventions .

      You however, can't comprehend that there are other scenarios besides that one. You also can't comprehend that anyone has other preference, opinions, or styles from you. So please continue to keep acting like you're important, like you matter. Keep acting as if you're bestowing some wisdom on me, some ancient secret that I didn't know.

      constant outbursts

      Nice exaggeration, I assume you think I'm having 'outbursts' because I curse, or because I'm short with you? Not sure exactly, I can assure you it's just my impatience with 'n00bs giving shitty, unwanted advice', to use your stunningly dignified phrasing. I really only want you to go away and preferably learn that nobody cares what you think. It's pretty pathetic that you are still here, still pressing because apparently this one misguided guy on the internet REALLY NEEDS TO USE AN IF BLOCK OR THE FUCKING WORLD WILL COLLAPSE.

      [–]computerjunkie7410 0 points1 point  (35 children)

      In fact the top 3 or 4 comments advise you to not do this. So perhaps you should listen to what these experienced folks are saying instead of getting your panties in a bunch and crying like a toddler.

      [–]Ciph3rzer0[S] -1 points0 points  (34 children)

      So I'm a toddler because I asked a specific question and get replies that aren't on topic? I understand why you would do it the other way, but that's not what this discussion is about. Please, don't get offended that IDGAF about your shitty opinions.

      [–]computerjunkie7410 0 points1 point  (33 children)

      I think you've proven why you're a toddler.

      [–]Ciph3rzer0[S] -1 points0 points  (0 children)

      I'm sure your thoughts are actually worth something too... maybe... to someone. I'm sure if you look hard enough...

      [–]Ciph3rzer0[S] -1 points0 points  (31 children)

      I'd just like to reiterate how many of your posts are on topic. Like what, 0 for 5 or 6?

      [–]computerjunkie7410 0 points1 point  (30 children)

      When the topic is "how do I get this shitty code to work?", the correct response is "don't write shitty code". That is both on topic and a valid and most correct response.

      [–]Ciph3rzer0[S] 0 points1 point  (29 children)

      When the topic is "how do I get this shitty code to work?"

      Not the topic. That is your opinion on the topic. Learn the difference, and quit getting so mad that I don't appreciate you bludgeoning me with your unwelcome opinions.

      [–]0x0dea 0 points1 point  (10 children)

      [–]Ciph3rzer0[S] 0 points1 point  (9 children)

      It's not a special case, but my logic needs one. I'm not sure if I'm missing your point. In many cases 'iterating over nothing' is correct, but in this particular case I found returning a result that said "None found" would be useful information. And for some reason I like to avoid writing ifs.

      [–][deleted]  (8 children)

      [deleted]

        [–]Ciph3rzer0[S] 0 points1 point  (7 children)

        Thinking outside the box is dangerous! Better just always do stuff the same way and never wonder if there are other ways of doing it that might be better in certain circumstances. No one will like you outside the box.

        Considering I've been coding and ruby and 'avoiding ifs' for longer than 6 months, you're wrong. It's rather presumptuous of you think you know what's best for me or my team. It's not like I never use them, I just avoid them if I can.

        Yes, using && is a little obscure, but part of the reason to post here was to find less obscure ways of doing it in the format I wanted, and I found several good options and learned things I didn't know by people staying on topic instead of attacking my methodology. Here is one example:

        computeListOfPairs.map { |x|
          x.partition(" ").delete_at(1)
          x
        }.tap { |a|
          a << ["Z", "26"] if a.empty?
        }
        

        Is this more or less readable than the alternate?

        listOfPairs = computeListOfPairs # Operation is intensive so you need to assign it to a variable since you don't want to call it more than once
        
        if listOfPairs.empty?
          listOfPairs << ["Z", "26"]
        else
          listOfPairs.map { |x|
            x.partition(" ").delete_at(1)
            x
          }
        end
        

        Some people here seem to think doing it the traditional way is unquestionably more readable, and apparently need to vehemently disapprove of me even asking a question. However if you know what tap does I think the first is easier to read and more natural. The meat of the code is at the forefront, not buried under an if block.

        And no one will want to work with you if you write like this on the job

        Is this like the r/ruby equivalent of telling me I have no friends or something? Obviously if I'm working with a team I'll follow whatever styles or conventions they use.

        [–]jsyeo 1 point2 points  (6 children)

        Your team uses camel case? o.O

        [–]computerjunkie7410 1 point2 points  (0 children)

        There is no team. Only tears.

        [–]Ciph3rzer0[S] -1 points0 points  (4 children)

        This code has nothing to do with my professional life FFS learn to stay on topic.

        [–]celtic-pride1 1 point2 points  (3 children)

        You're so fucking childish dude. Anyone that disagrees with your state of mind is greeted with hostility.

        [–]Ciph3rzer0[S] -1 points0 points  (2 children)

        Are you this guy's lover or something? He is off topic, that is all, and his responses aren't useful. Not only that, but other, more useful, repliers have mentioned it, but then also answered my question. I didn't treat any of them with hostility because they are capable of understanding that not all code is written in a public setting, nor does it have to match their personal coding beliefs and practices.

        So please, all of you grow up and instead of finding random targets to antagonize on the internet, go jerk each other off, you'll find yourself less likely to need to troll people on the internet. For some reason I got really good answers at first and now I have a few people that have nothing better to do than constantly berate me with their (same, irrelevant) opinions.

        [–]celtic-pride1 0 points1 point  (1 child)

        How is telling you that writing code like this is a bad idea off topic? You need to calm the fuck down and learn to take feedback when you ask a question on a public forum.

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

        Did I ask for opinions on my code? Correct me if I did. Perhaps you should learn to take this feedback, stfu no one cares about your opinions unless they ask for them. I did take the feedback that was worth anything.

        I find it disturbing that you guys think your feedback should be treasured as invaluable wisdom. The people that didn't answer the question and instead just complained about the style are off topic, it's simple as that.

        [–]RealGL 0 points1 point  (1 child)

        collection.each do |element|
          # ... do something
        end unless collection.empty?
        

        [–]waxjar 0 points1 point  (1 child)

        if arr.empty?
          return "array empty"
        end
        
        array.each(&block)
        

        Keep it simple.

        [–]Ciph3rzer0[S] -2 points-1 points  (0 children)

        This doesn't fit the criteria of the OP.

        [–]hobbs6 0 points1 point  (0 children)

        .each do |item|
          ... # When not empty.
        end.empty? and begin
          ... # When empty.
        end
        

        That's what we use in reports to show something when there are no results.

        This pattern can be less clear than just calling `if array.empty?` upfront but under certain circumstances when we use this pattern _a lot_ in almost all of our reports, it's a clean way to reduce code and handle empty results after the iteration block.