1

Looking for a little wisdom from fellow Rubyists. For a while now, I've used the following for convenience in some of my applications, and I'm wondering if there's a language feature I'm just missing:

class Object
  def as_block
    yield
  end
end

There are other uses, but the normal case for me is a conditional assignment that requires a little non-trivial logic. Two obvious ways to do this:

# via a second method:
def foo
  @foo ||= set_foo
end

# via conditional logic:
def foo
  if @foo
    @foo
  else
    # do complicated stuff
  end
end

Both of these approaches seem kind of ugly: in the first case, #set_foo seems extraneous, and the second just looks kind of nasty. So, instead, I like this:

def foo
  @foo ||= as_block do
    # do complicated stuff
  end
end

The problem here (aside from monkey patching Object) is that it's really a dependency (on the monkey patch) that looks like a language feature. That is, something like this really shouldn't be in (say) a Rails initializer---it seems like it should be in a gem, so the dependency can be managed correctly. Then I'm packaging an entire gem to run five lines of code to monkey patch Object...

So, my questions: 1. Anyone else use this, or something like it? 2. Has the Ruby team ever considered including something like this by default? It seems like a really easy way to use blocks as plain old expressions, but it's not there (as far as I know) which makes me wonder if there's some reason for not including it, or... 3. Is there already some better way of doing this that I'm just unaware of?

Thanks!

-E

Erik
  • 89
  • 4

2 Answers2

5

What you're looking for is begin ... end. This isn't the same thing as a block or Proc, as it's not an object you can pass around or a closure which creates a new scope, but it should serve your purpose just fine:

def foo
  @foo ||= begin
    # do complicated stuff
  end
end
Ajedi32
  • 45,670
  • 22
  • 127
  • 172
  • Yeah, I'm aware of the begin/end approach---actually, the first few times I ran into this, that's what I used. My complaint is that it's... well, it's broken from both semantic and style perspectives. First, when I'm reading code, begin/end constructs that aren't trapping exceptions don't look right. More importantly, begin/end blocks have weird properties; see, e.g., http://blog.newrelic.com/2014/11/13/weird-ruby-begin-end/ – Erik Jan 31 '15 at 20:15
  • 1
    @Erik I'm not sure why you think `begin ... end` doesn't look right while `as_block do ... end` does. IMO, this is one of the main reasons for using a `begin ... end` block, the other being exception handling. The special casing for creating a `do while` construct is indeed unfortunate, but it's really only a problem in that one case, and as long as you don't use it intentionally you're not going to run into that problem anyway. (E.g. Why would you write `begin ... end while ` when you really meant just meant `while ; ... end` in the first place?) – Ajedi32 Jan 31 '15 at 21:42
  • 1
    @Ajedi32, I did some looking around at this and it's true, it's not as problematic as I thought earlier. I'm not sure why, but like I said, my brain reads that looking for an ensure statement. Interesting discussion of it here: http://stackoverflow.com/questions/13279217/are-there-unintended-consequences-of-rubys-begin-end-without-rescue-use – Erik Feb 01 '15 at 02:44
  • @Erik Well, one person in the comments of the answer you linked suggested parenthesis as an alternative if you don't like `begin ... end`. Personally though, I prefer to avoid parenthesis for multi line expressions like this. – Ajedi32 Feb 01 '15 at 05:19
  • @Ajed32---Yeah, I'm not wild about parens there either. I'm going to do a little more research into the begin/end style---I may owe you an apology! I'll post back here. – Erik Feb 02 '15 at 17:47
  • @Erik No apology necessary. I don't take criticisms of the semantic features of Ruby personally. ;-) – Ajedi32 Feb 02 '15 at 18:30
2

You could use a lambda:

def foo
  @foo ||= lambda do
    # do complicated stuff
  end.call
end

Note that it is important to call the lambda to actually execute the expression, ie

def foo
  @foo ||= lambda do
    # do complicated stuff
  end
end

will return a lambda rather than your evaluated expression.

hjing
  • 4,922
  • 1
  • 26
  • 29