How can I write an IIFE in Ruby?
Asked Answered
E

2

6

In Javascript, an IIFE (Immediately-Invoked Function Expression, though there may be other expansions of the acronym) can be written as:

(function () {
  var foo = 'bar'
  console.log('hi!');
})();

where the variable declaration foo does not pollute the current scope. How can I do the same in a Ruby file?

I would like to create some temporary variables in a constrained scope (with closure), and make them inaccessible to other code later on in the file.

Emera answered 4/11, 2015 at 13:57 Comment(0)
N
3

Well, you do it the same way: write an anymous lambda literal, and immediately call it.

There is one caveat, though: Ruby does not have variable declarations. So, any use of a variable name inside a block which also exists outside the block refers to the captured outside variable, whereas in ECMAScript, if you declare var foo within a function, it will shadow the outside variable foo.

If you want the same behavior in Ruby, you have to explicitly declare foo as a block-local variable in the block's parameter list:

-> (;foo) do
  foo = 'bar'
  puts 'hi!'
end.()

If you don't do that, the assignment inside the block is going to modify a captured outside binding. Compare:

foo = 'foo'

-> do
  foo = 'bar'
  puts 'hi!'
end.()

foo
# => 'bar'

As you can see, the outside binding is modified.

foo = 'foo'

-> (;foo) do
  foo = 'bar'
  puts 'hi!'
end.()

foo
# => 'foo'

As you can see, only the second one actually matches the ECMAScript version:

"use strict";

const foo = 'foo';

(() => {
  const foo = 'bar';
  console.log('hi!');
})()

foo
// => 'foo'
Naturalist answered 4/11, 2015 at 15:17 Comment(0)
L
3
defined?(foo)
# => nil

# This defines a lambda, i.e. a closure with its own variable scope, among other things
# and directly executes it
lambda {
  foo = 'bar'
  puts foo
}.()

defined?(foo)
# => nil

In this example, we first check if the variable foo is defined, which it is not. Then, we create a new lambda object (which in Ruby is a special kind of Proc. and directly execute it.

Proc inherit the variables of the surrounding scope so that all local variables which are already defined can also be accessed (and changed) inside the proc. New variables defined in the proc only however will not be visible outside of the proc.

This can be verified with the final defined? statement.

Please note though that this technique is not very ruby-like. You should instead aim to create actual methods whoch you can then call. In order to encapsulate data and behavior you can also create small classes, even for one-off use which fully handle your behavior and all the required data.

Generally in Ruby, you tend to not pass anonymous closures around (i.e. anonymous functions in javascript or procs/lambdas/blocks in Ruby) but use full objects. If you use the technique above often in your code, you will earn concerned glances from experienced ruby developers.

Lute answered 4/11, 2015 at 14:15 Comment(4)
That looks weird in my ruby eyes... the parentheses after lambda are not needed and I never seen a block call like this... ->{ foo = "bar" }.call looks more ruby like imho.Interstratify
You are right with the parentheses. I tend to add them for lambdas out of habit, but you are right, they are not needed here. .() is exactly the same as .call; but I like the former better most of the time as it better transfers the function-like behavior of the procs. Finally, the new stabby lambda syntax looks weird with multi-line bodies in my eyes so I tend to avoid it here. In the end, it's all personal taste, both variants work exactly the same.Lute
Fun fact: .() is actually resolved by the parser as syntactic sugar for the call method. It works on all objects defining a call method, not only procz. This can be useful when e.g. creating dynamic service objects which look similar to methods but are actually complex dynamic things.Lute
you could go with instance_exec{ foo = 'bar'; p foo } as wellOribel
N
3

Well, you do it the same way: write an anymous lambda literal, and immediately call it.

There is one caveat, though: Ruby does not have variable declarations. So, any use of a variable name inside a block which also exists outside the block refers to the captured outside variable, whereas in ECMAScript, if you declare var foo within a function, it will shadow the outside variable foo.

If you want the same behavior in Ruby, you have to explicitly declare foo as a block-local variable in the block's parameter list:

-> (;foo) do
  foo = 'bar'
  puts 'hi!'
end.()

If you don't do that, the assignment inside the block is going to modify a captured outside binding. Compare:

foo = 'foo'

-> do
  foo = 'bar'
  puts 'hi!'
end.()

foo
# => 'bar'

As you can see, the outside binding is modified.

foo = 'foo'

-> (;foo) do
  foo = 'bar'
  puts 'hi!'
end.()

foo
# => 'foo'

As you can see, only the second one actually matches the ECMAScript version:

"use strict";

const foo = 'foo';

(() => {
  const foo = 'bar';
  console.log('hi!');
})()

foo
// => 'foo'
Naturalist answered 4/11, 2015 at 15:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.