I came across a weird situation when coding ruby:
def foo(bar=true,baz,*args)
end
Raises this error:
syntax error, unexpected tSTAR
Whereas these:
def foo(bar,baz=true,*args)
end
def foo(bar=true,baz=true,*args)
end
are okay.
To add to the strangeness, these:
def foo(bar,baz=true)
end
def foo(bar=true,baz)
end
both work.
However, this:
def foo(bar=true,baz,args=true)
end
raises an error, but these:
def foo(bar=true,baz=true,args)
end
def foo(bar,baz=true,args=true)
end
are fine.
I figure there is a logical reason for why certain combinations are allowed and others aren't, but I can't find them anywhere, either through google or searching stackoverflow. My question is simply: Why are some of these combinations allowed while others aren't?
Ruby books out there explain this in detail. But even without one, you will get used to the logic of it. A decent coder defines functions with compulsory ordered parameters going first, like this:
def foo( a, b, c=0, d=0 )
# ...
end
Simply, first 2 args go to a, b, and up to 2 optional args to c, d, in that order. But you are granted freedom beyond common decency:
def foo( a=0, b )
# ...
end
If you supply 1 argument, it goes to b. If you supply 2 args, they get assigned to a, b, in that order. This is less decent, because it takes more learning from the user. If you yourself are the user, you are being indecent to yourself, but still, there is no syntactic ambiguity at all. Now look at this:
def foo( a=0, b, c=0 )
# ...
end
If you call foo( 1, 2 ), did you mean that a = 1, b = 2, c = 0, or a = 0, b = 1, c = 2? Perhaps first one is more likely, but the rule of coding is, do not guess the user's intentions. So, probably, Ruby team decided not to pamper this syntax. You can still achieve it by defining:
def foo( *args )
a, b, c = case args.size
when 1 then [0, args[0], 0]
when 2 then [*args, 0]
when 3 then args
else fail ArgumentError; "#{args.size} arguments for 1..3!" end
# ...
end
Thereby, in fact, you are granted absolute freedom, but Ruby guides you to good practice: By making good code look better than bad code. Same applies to collecting arguments with * splat. The decent case is unambiguous:
def foo( a, b=0, *c )
# ...
end
But with:
def foo( a=0, b, *c )
# ...
end
When you call foo( 1, 2, 3 ), did you mean that a = 1, b = 2, and c = [3], or a = 0, b = 1, and c = [2, 3]? Therefore, again, this second case is not encouraged, but nevertheless achievable:
def foo( *c )
a, b = case c.size
when 0 then fail ArgumentError, "Too few arguments!"
when 1 then [0, c.shift]
else [c.shift, c.shift] end
# ...
end
Writing functions with many arguments, and with many ordered arguments in particular, is bad practice. There is an old saying, that binary method is better than ternary, unary better than binary, and finally, nullary one better than unary. If your method craves many arguments, you should refactor it by replacing it with an object. Does this answer your question?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With