Reputation: 5251
I am reading through createyourlang book and saw a code snippet that looks like this:
tokenizer = if identifier = chunk[IDENTIFIER, 1]
IdentifierTokenizer.new(identifier, tokenizer).tokenize
elsif constant = chunk[CONSTANT, 1]
ConstantTokenizer.new(constant, tokenizer).tokenize
elsif number = chunk[NUMBER, 1]
...
I find it confusing having two equal signs on the same line. What does it mean to have A = if B = C
?
If you're wondering what chunk is, assume chunk is string "hello"
and chunk[IDENTIFIER,1]
equals to "hello"
and the other chunk[OTHER_CONST,1]
equals to nil
. The function works. You can find the source code repo here. I am mainly curious how to read this function/ if there is a better way to rewrite this code to make it more readable?
Upvotes: 0
Views: 1079
Reputation: 110675
Yes, it can be confusing, in part because one's first reaction to seeing if identifier = chunk[IDENTIFIER, 1]
may be that it's probably a bug, that the author meant if identifier == chunk[IDENTIFIER, 1]
.
The code block given in the question is probably equivalent to:
tokenizer =
if chunk[IDENTIFIER, 1]
IdentifierTokenizer.new(chunk[IDENTIFIER, 1], tokenizer).tokenize
elsif chunk[CONSTANT, 1]
ConstantTokenizer.new(chunk[CONSTANT, 1], tokenizer).tokenize
elsif chunk[NUMBER, 1]
NumberTokenizer.new(chunk[NUMBER, 1], tokenizer).tokenize
..
Rather than computing chunk[IDENTIFIER, 1]
, chunk[CONSTANT, 1]
and chunk[NUMBER, 1]
twice, those values are assigned to variables (identifier
, constant
, number
) the first time they are calculated. (Those assignments are evaluated before if
and elsif
are applied.) This is commonly done when such calculations are costly or have an undesired side effect when performed more than once. The use of temporary variables in this way also DRYs the code, but other approaches that achieve the same objective may be preferable. I personally avoid such inline variable assignments, in part because I regard them as ugly.
One alternative that may be more clear, and which DRYs the code even more, is the following.
tokenizer =
tokenit(IDENTIFIER, IdentifierTokenize, tokenizer) ||
tokenit(CONSTANT, ConstantTokenize, tokenizer) ||
tokenit(NUMBER, NumberTokenize, tokenizer) ||
...
def tokenit(type, class, tokenizer)
ch = chunk[type, 1]
if ch
class.public_send(:new, ch, tokenizer).tokenize
else
false
end
end
This of course assumes that when ch
is truthy class.public_send(:new, ch, tokenizer).tokenize
is also truthy.
Upvotes: 6