14

I call a method with a block;

method do
  "Hello"
end

and the method is defined as;

def method
  yield
end

and when defining method; i want to check if given block is empty (nil) or not, because the variable in the method may end up like this;

method do
  ""
end

So in definition, i want to check if the yield block is nil or not. Like;

def method
  if yield ? yield : "Empty block? Seriously?"
end

I know the above does not work. Bu it is what i want to achieve.

Also keep in mind that block_given? will always be "true" since the block is given even if it is nil or empty string.

UPDATE: As most of the comments/answers state that the question is unclear; here is the problem simplified by @ndn:

I want to check if the result of executing a block is "empty"(nil or "") without invoking it first.

tozlu
  • 4,667
  • 3
  • 30
  • 44
  • If I understand you correctly, you want to know if the value returned by the block is `nil` or `""` before yielding. This is not possible. If that if not the case, you can just assign the result of `yield` to a variable and make `nil?` and `empty?` checks on it. – ndnenkov Aug 02 '15 at 13:31
  • Yes, you are correct. – tozlu Aug 02 '15 at 14:17
  • Yes if i assign it like in @WandMaker's answer, it gives error if block is not given. So you say there is no possible way to learn if the block content is nil or "" before yielding. Is the a source or discussion, etc. about this topic so that i learn more? Thanks. – tozlu Aug 02 '15 at 14:26
  • Ok, let me write an answer... – ndnenkov Aug 02 '15 at 14:27

3 Answers3

22

It is unclear what you are asking, because a block itself can not be empty. Therefore, you might mean a few different things:

  1. A missing block. You can check if a block is given

    block_given?
    
  2. Block with empty body (aka {} or do end). This is not impossible, but requires some advanced voodoo ruby metaprogramming magic. Generally, if this is what you are looking for, either you are writing something very interesting or your approach is completely wrong.
  3. You want to check if the result of executing a block is "empty" without invoking it first. This is impossible. For example, consider the following block:

    { [nil, "", true].sample }
    

    Obviously, there is no way to know in advance.

  4. You are ok with calling the block. Then you can assign the result to a variable and make checks on it:

    def some_method
      evaluation_result = yield if block_given?
      if evaluation_result.nil? or evaluation_result == ""
        # do something if the block was not given or the result is nil/empty
        puts "Empty block? Seriously?"
      else
        # do something if the block was given and the result is non nil/empty
        puts evaluation_result
      end
    end
    

    Now when you invoke some_method:

    some_method { "something" } # => "something"
    some_method { 3 + 5 } # => 8
    some_method { nil } # => "Empty block? Seriously?"
    some_method { "" } # => "Empty block? Seriously?"
    some_method { } # => "Empty block? Seriously?"
    some_method # => "Empty block? Seriously?"
    

EDIT: A workaround for case #3 might be to create two procs, one with what you want to do if the block is "empty" and one - if it is not, then pass them around to the endpoint where you will finally invoke the block. This might or might not be applicable depending on your exact situation.

EDIT2: Another workaround can be to redefine the Proc#call method for your proc instances. However, this doesn't work for yield:

def secure(&block)
  insecure_call = block.method(:call)
  block.define_singleton_method(:call) do
    insecure_call_result = insecure_call.call
    if insecure_call_result.nil? or insecure_call_result == ""
      "<b>Bummer! Empty block...</b>"
    else
      insecure_call_result
    end
  end
end

x = proc { }
y = proc { "" }
z = proc { nil }
a = proc { 3 + 5 }
b = proc { "something" }
u = proc { [nil, "", true].sample }
[x, y, z, a, b, u].each { |block| secure &block }

# some method that uses the block
def user(&block)
  "What I got is #{block.call}!"
end


user &x # => "What I got is <b>Bummer! Empty block...</b>!"
user &y # => "What I got is <b>Bummer! Empty block...</b>!"
user &z # => "What I got is <b>Bummer! Empty block...</b>!"
user &a # => "What I got is 8!"
user &b # => "What I got is something!"
user &u # => Different each time

EDIT3: Another alternative, which is sort of cheating, is to wrap the given proc in another proc. This way, it will work for yield too.

def wrap(&block)
  proc do
    internal_proc_call_result = block.call
    if internal_proc_call_result.nil? or internal_proc_call_result == ""
      "<b>Bummer! Empty block...</b>"
    else
      internal_proc_call_result
    end
  end
end

Now using the result of wrap and will get you behavior similar to secure.

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • Thanks for detailed explanation. My case is 3. I'm developing a helper class for rails, and it is possible that an empty variable will come in a block. (Especially when the block is sent dynamically via a variable, and that variable happens to be _nil_ or _""_). So in my helper, i have to check if block is empty, and build correct html output depending on that. If i do like in 4th method (also @WandMaker's answer), the yield result is output when i make the assignment to __evaluation_result__. But i just want to check there and output the _yield_ at some other place. – tozlu Aug 02 '15 at 15:14
  • 1
    Please don't use "empty variable" or "empty block" as an expression (the latter might be ok if it is clear that you mean the #2 case). If you indeed mean #3, as I said - it is not possible. Simple sanity checks to prove this would be a block that returns a result randomly (as in the answer above) or one that never returns (for example - endless loop). As @Jörg said, this is known as the Halting Problem and is unsolvable. – ndnenkov Aug 02 '15 at 15:24
  • I'm accepting this as answer, as this is the most detailed and to-point explanation of the problem. Thanks. – tozlu Aug 02 '15 at 16:19
  • @1.44mb, yet another alternative. :) – ndnenkov Aug 02 '15 at 17:00
  • 1
    I see, a block containing 10,000 lines of code that returns `nil` is "empty". – Cary Swoveland Aug 02 '15 at 19:05
  • @CarySwoveland, don't kill the messenger. – ndnenkov Aug 02 '15 at 20:20
  • Sorry, I only meant to [wing you](http://www.oxforddictionaries.com/definition/english/wing) (2.1). – Cary Swoveland Aug 02 '15 at 23:19
6

UPDATED Answer My last effort to simplify the answer based on comments..

You can check for block emptiness with block_given? and you need to explicitly check for yield output for emptiness like below

def method(&block)

    # Below if condition is to prove that block can be accessed 
    if  block_given? 
        p block
        p block.yield
    end

    b = yield if block_given?
    (b.nil? || b.empty?) ? "Empty block? Seriously?" : b
end


p method {"Hello"} # inline block
result = method do 
      "World" 
    end
p result   
p method # No blocks provided
p method {""} # Block that returns empty string

Output of the program

"Hello"
"World"
"Empty block? Seriously?"
"Empty block? Seriously?"
Community
  • 1
  • 1
Wand Maker
  • 18,476
  • 8
  • 53
  • 87
  • 1
    How is this different from `block_given?` which the OP says doesn't solve his problem? (Although it is unclear from the question what the OP's problem even is.) – Jörg W Mittag Aug 02 '15 at 13:21
  • @JörgWMittag Both seems to do same thing. I missed block_given? part, besides being a newbie myself. If I use `block_given? ? ...` instead of `block ?` - get same output. May be I misunderstood the question. OP is asking how to check whether output of yield was blank string – Wand Maker Aug 02 '15 at 13:23
  • @JörgWMittag I'm sorry that my question is not clear. I tried be as specific as i could. – tozlu Aug 02 '15 at 14:19
  • @WandMaker I just checked this, _b = yield if block_given?_ actually yields the block. So _yield_ is yielding the block (and throwing error if no block is given) when called even if it is called for assignment. So no help. But thanks. – tozlu Aug 02 '15 at 14:24
  • @1.44mb I have provided 4 test cases in my solution. Can you tell me which additional test case you are running for which you see an issue? – Wand Maker Aug 02 '15 at 14:26
  • _b = yield if block_given?_ yields the block immediately instead of assigning it to b. But i just want to check there, and yield later. So other answers have stated that what i want is impossible. Thanks for the effort, you pointed me in the right direction to solve my problem in another aspect. – tozlu Aug 02 '15 at 15:21
  • @1.44mb I have updated the answer to allow access to block, that was part of my original answers, but I removed it during various revisions – Wand Maker Aug 02 '15 at 16:24
  • There is no concept in Ruby of a block being `empty` or `nil`. It is not helpful to perpetuate such nonsense. `method` returns `"Empty block? Seriously?"` if the block returns an object `obj` which is `nil` or responds to `empty?` and for which `obj.empty? #=> true`. So you would conclude that `{ '' }`, `{ [] }` and `{ {} }` are all "empty blocks" (even though they contain Ruby objects). What about `{ A.new }`, `{ ->{} }`, `{Time.now}`, and `{true}`? They (and many other blocks) raise the exception `"undefined method 'empty?'..."`. @Jörg has it right. – Cary Swoveland Aug 02 '15 at 18:51
6

If I understand correctly, you want to statically determine what the runtime value of a block is. This is one of the many known impossible problems resulting from the undecidability of the Halting Problem.

In other words: it can't be done.

Not "it can't be done in Ruby", not "it is hard", it simply can't be done, period. And it can be (and has been) mathematically proven that it can't be done. Ever.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Thanks Jörg for the clear explanation. I'm satisfied, yet i'm hungry for more reading about the technical reasons behind this (from both programming and mathematical aspects). – tozlu Aug 02 '15 at 15:16
  • The [Wiki](https://en.wikipedia.org/wiki/Halting_problem) is a good starting point. – Cary Swoveland Aug 02 '15 at 18:56