1

I thought that:

do_something if condition

were equivalent to

if condition
  do_something
end

I found a code that does not respect this rule.

if !(defined? foo)
  foo = default_value
end

Here, foo takes default_value.

foo = default_value if !(defined? foo)

Here, foo takes nil. In the former code, I think if is executed first, and should be equivalent to:

foo = (default_value if !(defined? foo))

Is there any way to set to default_value if the variable is not defined?

General answer :

Some several comments want to use the ||= operator... Which will not work if foo is nil :

foo ||= default_value

will return the default value, while foo is defined.

I insist on using "not defined?", which is not equal to nil.

pierallard
  • 3,326
  • 3
  • 21
  • 48
  • is this a typo? -> if (defined? foo) #in the third code block, cause in the fourth its -> if !(defined? foo) – ajt Aug 14 '13 at 12:20
  • @ajt You're right. I forgot the '!'... – pierallard Aug 14 '13 at 12:29
  • 1
    Idiomatic Ruby would use `||=`, are you opposed to that? – Dave Newton Aug 14 '13 at 12:30
  • @DaveNewton No, because it works too with a `nil` value... – pierallard Aug 14 '13 at 12:31
  • @ForgetTheNorm What do you mean "works too with a `nil` value"? Do you mean you don't want `foo` to be set if it's `nil`? – Dave Newton Aug 14 '13 at 12:33
  • 1
    The question is inaccruate; the `if` is not what changes things here - it's your parenthetical scope. I didn't hedge a re-write, since it would massively change the nature of the question – New Alexandria Aug 14 '13 at 12:35
  • what do you mean by 'not work if foo is nil'? of course it will work - it will set the foo to nil||5 -> == 5. It just has different meaning than 'defined?'. But it certainly **works**. The problem is at the other side of the keyboard, IMHO. You should not use the existence of a variable as a free-form flag or option. If you want to have a set of optionally-existing variables with any value, make a HASH and put the values under the KEYS in that hash. Instead of 'foo' equal to 'nil', '5', or being undeffed, make a hash and under the key of :foo keep a nil, or 5, or don't set the key at all. – quetzalcoatl Aug 14 '13 at 12:40
  • If you try to rely upon 'defined?', you will have to strictly learn and adhere all the parsing rules and even order of the parsing of the language (or even your current version of the interpreter). In this context, **sava**'s answer is the only proper explanation of why this happens. But, it may change at some point of time in some future version. Do you really want to rely on that? Believe me, use a hash, not `defined?`. – quetzalcoatl Aug 14 '13 at 12:46
  • See my answer. As long as the assignment comes after the condition in terms of linear order, the problem does not arise. – sawa Aug 14 '13 at 12:49
  • 1
    @quetzalcoatl I use the method you described with a `hash` in the function definitions, for optional values, using `{default_values}.merge(options)`. Here is the context of Rails partial views, where locals where passed as a hash (`:locals => { }`). In the partial file, the only method I know to say 'variable is set as local or not' is to use `defined?`. If you have an other idea, share it please ! – pierallard Aug 14 '13 at 12:54
  • I don't use Rails, so maybe there's some neat trick.. but I've just found this question/answers: http://stackoverflow.com/a/17342307/717732 Please note the `local_assigns` (auto)variable that seems to be a hash that keeps all the information about what local was assigned to what value (probably it directly is the same hash object that you pass with the `:locals` key as parameter). If it's correct, then you can easily check if a `foo` is a defined-local just by checking if that hash has a key of :foo. Pay care to the answer I've linked. **fetch** does **exactly** what you want, even for `nil`. – quetzalcoatl Aug 14 '13 at 19:04
  • @quetzalcoatl Oh, `local_assigns` is exactly what I need... Code will be nicer from now. Thanks, thanks, thanks. – pierallard Aug 16 '13 at 10:23

2 Answers2

5

The Ruby way is

foo ||= default_value

But, of course

if (defined? foo)
  foo = default_value
end

and

foo = default_value if !(defined? foo)

are different. You're not comparing the same thing.

In one you compare (defined? foo) and the other you compare !(defined? foo)

I think what you're really after is the following

if !(defined? foo)
  foo = default_value
end
deefour
  • 34,974
  • 7
  • 97
  • 90
  • Sorry, I forgot the `!` on my first post... This changes all the context of this question. Sorry. – pierallard Aug 14 '13 at 12:29
  • Just test the 2 expressions of the if (3 lines and 1 line), in the same context ! The result of `foo` after is not the same... – pierallard Aug 14 '13 at 12:41
3

The two pieces of code are equivalent syntactically, but are different from the point of view of parsing. You are partially right that, "if is executed first", but that is only regarding syntax. Within parsing, the parsing order follows the linear order of the tokens. In Ruby, when you have an assignment:

foo = ...

then foo is assigned nil even if that portion of code is not syntactically evaluated, and that affects the result of defined?.

In order to write inline without having that problem, the way I do is to use and, or, &&, or ||:

defined?(foo) or foo = default_value
sawa
  • 165,429
  • 45
  • 277
  • 381
  • Yes, I feel that you understand what I mean. Is there a beautiful writing to place around the assignment, like { foo = xxx } if condition ? – pierallard Aug 14 '13 at 12:42
  • no, if you start with foo=, it will get parsed and partially evaluated. You have to have the condition before the 'foo=' expression. Even explicit `begin foo=5 end if defined(foo)` causes the foo to get auto-defined. Probably the only sane way is that `or` example that sawa has just provided. – quetzalcoatl Aug 14 '13 at 12:49
  • `defined?(foo) or foo = default_value`, and in one fell-swoop we've made Ruby look like Perl. :-) – the Tin Man Aug 14 '13 at 14:28
  • @theTinMan I think it still preserves readability. In English, you can naturally paraphrase: `"You will miss the bus unless you wake up."` as `"Wake up, or you will miss the bus."` – sawa Aug 14 '13 at 14:55
  • Oh, I understand its readability and everything. It's just not idiomatic Ruby. With minor changes it'd be very idiomatic Perl, but I've written in Perl so long I think in it, which is why I find it readable... and which is pretty disgusting at times. – the Tin Man Aug 14 '13 at 15:16