Why should you avoid the then keyword in Ruby? - ruby

It's mentioned in several Ruby style guides that you should "Never use then." Personally, I think the "then" keyword allows you to make code denser, which tends to be harder to read. Is there any other justification for this recommendation?

I almost never use the then keyword. However, there is one case where I believe it greatly improves readability. Consider the following multi conditional if statements.
Example A
if customer.jobs.present? && customer.jobs.last.date.present? && (Date.today - customer.jobs.last.date) <= 90
puts 'Customer had a job recently'
end
Line length too long. Hard to read.
Example B
if customer.jobs.present? &&
customer.jobs.last.date.present? &&
(Date.today - customer.jobs.last.date) <= 90
puts 'Customer had a job recently'
end
Where do the conditions end and where does the inner code begin. According to must Ruby Style Guides, you to have one extra space of indentation for multi line conditionals, but I still don't find it all the easy to read.
Example C
if customer.jobs.present? &&
customer.jobs.last.date.present? &&
(Date.today - customer.jobs.last.date) <= 90
then
puts 'Customer had a job recently'
end
To me, Example C is by far the most clear. And it is the use of the then that does the trick.

If I remember correctly, then is just one of the delimiters to separate the condition from the true part (semicolon and newline being the others)
if you have an if statement that is a one-liner, you'd have to use one of the delimiters.
if (1==2) then puts "Math doesn't work" else puts "Math works!" end
for multi-line ifs, then is optional (newline works)
if (1==2)
puts "Math doesn't work"
else
puts "Math works!"
end
Could you post a link to one of the style-guides that you mention...

I think "never use then" is wrong. Using too much non-alphabet characters can make code as difficult to read as perl or APL. Using a natural language word often makes the programmer more comfortable. It depends on the balance between readability and compactness of the code. Ternary operator is occasionally convinient, but is ugly if misused.

Related

Sample ruby program keeps running with errors, can't figure them out [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
Alright, I have tried my best to get this code to just run without spitting out errors, but to no avail. Hopefully you can help me out.
require 'launchy'
#def linebreak(breakline)
def program()
puts "Welcome to test program v1. Would you like to continue? ENTER y for Yes or n for no"
user_input_1 = gets.chomp
if user_input_1 == "y"
puts "How would you like to proceed CRASH | TEXTMAKER | UNDECIDED // CASE SENSITIVE"
user_input_2 = gets.chomp
if user_input_2 == "CRASH"
while true Launchy.open("http://google.com")
elsif user_input_2 = "TEXTMAKER"
while true out_file.puts("test program v1")
else
puts "You have not entered a method."
elsif user_input_1 == "n"
abort
else
puts "That is not a valid command. Please run the script again."
end
end
Alright there's a few problems, but don't worry everything can be
fixed!
Let's start with what you did well
Good job using your booleans in most cases, most beginners don't seem
to grasp that == means equal, and = means something completely
different
Good job with the puts and using it properly, there are other
methods which I will cover later, that will look much better in your
case.
Now let's cover what can be fixed
As I stated above in most cases you used your Boolean correctly
however you seem to have missed one. elsif user_input_2 = "TEXTMAKER" you need a == to show that it is equal.
Your while loop doesn't seem to make any sense because you don't ever really set anything as true. In Ruby it is highly
recommended to never use a while loop, of course there are times
that you have to, but only after you've tried everything else.
elsif; else; elsif never use an elsif after an else. elsif is to provide an exception, in your case there's more then
one option so use the elsifs before the else, you'd be better using a case statement here.
Not to sound like an ass, but your indentation is horrible. You'd find a lot more problems with good indentation, in Ruby that's two
spaces, here's the style guide:
https://github.com/bbatsov/ruby-style-guide
Never have your users options be case sensitive, if somebody else is going to use your program, just assume they are the dumbest people in the world and make it super easy for them. user_input_2.gets.chomp.upcase this will set it so that no matter how they enter their text, it will always be upper case.
You're missing an end this will become clear once you've used proper indentation
Alright let's rewrite this thing:
require 'launchy'
def program #<= you don't need the parentheses
print "Welcome to the test program version 1, to continue type 'c' to exit type 'e': "
user_input = gets.chomp.upcase
if user_input == "C"
program_choices
elsif user_input == "E"
puts "Exiting.."
exit
#if you really want to use abort
#abort("Exiting..")
#you won't need to use exit if you use abort
else
puts "Invalid input, try again"
program #<= this is a loop known as recursion, don't use it to much
end
end
def program_choices
print "Pick an option, the choices include CRASH | TEXTMAKER | UNDECIDED: "
user_input = gets.chomp.upcase
case user_input #<= this is how you start a case statement
when "CRASH"
Launchy.open("http://google.com")
when "TEXTMAKER"
write_test_data
when "UNDECIDED"
puts "You really need to make up your mind.." #<= have some fun with it, that's the point of programming.
program_choices
else
puts "Invalid input, try again"
program_choices
end
end
def write_test_data
x = "Test file version 1"
if File.exist?("path/to/test/file.txt")
File.open("path/to/test/file.txt", "a"){ |s| s.write(x) }
else
new_test_file = File.new("path/to/test/file.txt")
new_test_file.puts(x)
end
end
And bam! Your program is up and running! If you have any questions about anything don't hesitate to ask, programming can be very tricky, and it can be very hard. Keep it up and you'll get there!
I see plenty of problems.
This isn't valid:
while true Launchy.open("http://google.com")
Neither is this:
while true out_file.puts("test program v1")
I can't tell you how to fix this, though, since it's not at all clear what you're trying to do.
An if block may only have one else, and it must come after all elsifs.
Here:
elsif user_input_2 = "TEXTMAKER"
You're assigning a new value to user_input_2. I'm guessing you meant to use the equality operator, which is ==.
Your def block doesn't have an end. This became obvious after I edited your code to use proper indentation. You'll save yourself a lot of trouble by using sensible indentation and whitespace.

Can I put an if/unless clause on the next line in Ruby?

In Perl, I often find myself using the following pattern:
croak "incompatible object given: $object"
unless $object->isa('ExampleObject') and $object->can('foo');
I tried to translate this into Ruby like this:
raise ArgumentError, "incompatible object given: #{object.inspect}"
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
But that does not work because Ruby interprets unless as the start of a new statement. As far as I understand, I can put a backslash at the end of the first line, but that looks ugly and feels wrong. I could also use a regular unless condition raise error end structure, but I like the style of the original form more. Is there a nice (and idiomatic) way to write this as a single statement in Ruby?
Can I put an if/unless clause on the next line in Ruby?
You can't. From page 107 (PDF page 127) of the final draft of ISO Ruby which usually isn't relevant, but basic things like this are and it also spares us from having to read parse.y:
unless-modifier-statement ::
statement [no line-terminator here] unless expression
This is pretty clear. It just doesn't get more similar to your Perl example than:
raise ArgumentError, "incompatible object given: #{object.inspect}" unless
object.is_a?(ExampleObject) and object.respond_to?(:foo)`
or:
raise ArgumentError, "incompatible object given: #{object.inspect}" \
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
Just as you feel wrong to put a backslash at the end to force a single line statement, it is wrong to use a single line statement when it extends beyond a single line.
This is not really a solution, I was sloppy when reading the question. The OP wants a solution without backslash.
You should be able to do this:
raise ArgumentError, "incompatible object given: #{object.inspect}" \
unless object.is_a?(ExampleObject) and object.respond_to?(:foo)
The \ characters tells ruby to keep reading as if there was no line break.
As far as I know there is no other way than a \, since otherwise, as you already said, Ruby thinks it's a new statement.
Keep in mind that style guides and conventions differ from language to language. In Ruby I'd not expect an if/unless statement in a line coming after it's code. In fact I even dislike putting if/unless at the end of a line, since it reverses the reading direction from If this, then that to that, if this (then what? Ah, I need to read back again), especially when the condition is more complex than raise 'foo' if bar.empty?.
In Perl and other languages though this might be different, since you have other conventions, style guides and this ;-thingy ;)

trying to find documentation on ruby's trailing if usage

I'm working in the RSpec book (page 121) and am being presented with a bit of code that is apparently self evident and clear. It's not self evident for me, and I'm hoping someone can help me understand.
I'm coming to ruby from c# so please use small words :)
Here's the original code
def total_match_count
count = 0
secret = #secret.split('')
#guess.split('').map do |n|
if secret.include?(n)
secret.delete_at(secret.index(n))
count += 1
end
end
count
end
here's the refactor
def total_match_count
secret = #secret.split('')
#guess.split('').inject(0) do |count, n|
count + (delete_first(secret, n) ? 1 : 0)
end
end
def delete_first(code, n)
code.delete_at(code.index(n)) if code.index(n)
end
Again, this is supposed to be so obvious as to need no comment.
I'm not understanding the trailing "if code.index(n)" bit and I can't find any documentation on using the keywords "ruby trailing if"
Obviously I'm missing something basic.
Ruby Post-Conditions as Syntactic Sugar
In Ruby, almost everything is an expression, and keywords like if and unless can be used as expression modifiers that follow an expression. Some languages refer to these as post-conditions, but the general idea is that:
if 1 == 1
puts true
end
is intended to be equivalent to:
puts true if 1 == 1
The post-condition can sometimes make the intent of the code clearer, or create a more natural flow. The parser differentiates between the :if and :if_mod tokens that internally represent the "normal" if-statement and its matching post-condition, but from a programmer's perspective the post-conditions are (or should be) largely syntactic sugar to make certain expressions easier or cleaner to read and write.
You don't ever need post-conditions in Ruby, but you will often find them in idiomatic Ruby code. If you don't grok them, or don't find that they improve the readability of your code, then feel free to ignore them until and unless they seem useful to you.
This:
code.delete_at(code.index(n)) if code.index(n)
is the same as this:
if code.index(n)
code.delete_at(code.index(n))
end
Some people think the one-liner is easier to read. It's a matter of style--when lines become long, the "trailing if" can be a gotcha, as you might not think to read to the end of the line to realize it has a condition attached. Use judiciously.
Ruby also has unless, which can be used in the "trailing" form too:
do_stuff unless no_on_second_thought

Ruby idiom for do_two_simple_things if something_is_true

For instance, this takes 4 lines which is too much space for such a simple operation:
if something_is_true
puts 'error'
return
end
this one is a one-liner but looks awkward.
if something_is_true; puts 'error'; return; end
Can we do something like
# it would be great if this would work because it is short and readable
puts 'error' and return if something_is_true
I'm not sure why you think space is at such a premium that your original code is "too much." Give your code room to breathe, and let it take the space it needs. Getting too tricky in order to "save space" is a false economy. The most important thing is that your code be readable and understandable. Your original code looks great to me.
I agree with #NedBatchelder that your original code is probably best. Others have pointed out that in your particular example, you can use return puts 'error'.
Still, for the sake of learning, you can group multiple statements with parentheses, and therefore use a small number of statements in places where you could otherwise use only one. You said:
# it would be great if this would work because it is short and readable
puts 'error' and return if something_is_true
You can do this with:
(puts 'error'; return) if something_is_true
This is kind of awful but I think it will work because puts returns nil:
puts 'error' || return if something_else
In this specific case, return without a value will return nil; as this happens to also be the return value of puts, you can get the same effect with just:
return puts "error" if something_else
Someday you will probably care less about how many cycles you can spend in a single line of code. I would use the if-end block because it is simple, clear, and... you know, that's what it's there for.
I'd recommend never to use ; to join statements, it tends to be unreadable. But still there are other approaches, two ideas: first one, join error(msg, return_value = nil) and return:
return(error("Message")) if something_is_true
return(error("Message", value_to_be_returned)) if something_is_true
Second one, in Ruby it's idiomatic to signal problems using exceptions, so you can write this perfectly idiomatic one-liner:
raise MyException.new("human explanation of the error") if condition
The same idea used in an assignment:
link = doc.at_css(".content a.link") or raise MyException.new("msg")

string parsing optimization : ruby

I am working on a parser that is currently way too slow for my needs (like 40x slower than I would like) and would like advice on methods to increase my speed. I have tried and am currently using a custom regex parser, aswell as a custom parser using strscanner class. Ive heard a lot of positive comments on treetop, and have considered trying to combine the regex into one huge regex that would cover all matches, but would like to get some feedback w/ experience before I rewrite my parser yet again.
The basic rules of the strings that I am parsing are:
3 segments (BoL operators, message, EoL operators)
~6 BoL operators
BoL operators can be in any order
2 EoL operators EoL operators can be in any order
Quantity of any specific operator can be 0, 1, or >1 but only 1 is used rest are removed and discarded
Operators in the 'message' section of the string are not captured / removed
Whitespaces is allowed before & after operators but not required
Some BoL operators can have whitespace in the setting
My current Regex parser works by running the string through a loop that checks for BoL or EoL operators 1 at a time and cutting them out, ending the loop when there are no more operators of the given type as so...
loop{
if input =~ /^\s+/ then input.gsub!(/^\s+/,'') end
if input =~ /reges for operator_a/ #sets
sets operator_a
input.gsub!(/regex for operator_a)/, '')
elsif input =~ /regex for operator_b/
sets operator_b
input.gsub!(/regex for operator_b/,'')
elsif input =~ /regex for operator_c/
sets operator_c
etc .. etc .. etc..
else
break
end
}
The question I have, What would be the best way to optimize this code? Treetop, another library/gem that I have not found yet, combining the loops into one huge regex, something else?
Please restrict all answers and input to the Ruby language, I know that it is not the 'best' tool for this job, it is the language that I use.
More specific grammer / examples if that helps.
This is for parsing communication commands sent to a game by users, so far the only commands are say, and whisper. The begenning of line operators accepted are ::{target}, :{adverb}, ={verb}, and #{direction of}. The end of line operators are {emoticon (aka. :D :( :)}, which sets adverb if not already set, and end of line puncutation which sets verb if not already set.
the character ' is an alias for say, and sayto is an alias for say::
examples :
':happy::my sword=as# my helm Bol command operators work.
{:action=>:say, :adverb=>"happily", :verb=>"ask", :direction=>"my helm", :message=>"Bol command operators work."}
say yep say works
{:action=>:say, :message=>" yep say works"}
sayto my sword yep sayto works as do EoL operators!:)
{:action=>:say, :target=>"my sword", :adverb=>"happily", :verb=>"say", :message=>"yep sayto works as do EoL operators!"}
whisper::my friend : happy Bol command operators work with
whisper.
{:action=>:whisper, :target=>"my friend", :adverb=>"happily", :message=>"Bol command operators work with whisper."}
whisp:happy::tinkerbell and they work in a different order.
{:action=>:whisper, :adverb=>"happily", :target=>"tinkerbell", :message=>"and they work in a different order."}
':bash=exclaim::hammer BoL operators work in this order too.
{:action=>:say, :adverb=>"bashfully", :verb=>"exclaim", :target=>"hammer", :message=>"BoL operators work in this order too."}
sayto bells =say :sad #wontwork Bol > Eol and directed !work with
directional? :)
{:action=>:say, :verb=>"say", :adverb=>"sadly", :direction=>"wontwork", :message=>"Bol > Eol and directed !work with directional?"}
'all EoL removed closest to end used and reinserted. !!??!?....... :)
? :(
{:action=>:say, :adverb=>"sadly", :verb=>"ask", :message=>"all EoL removed closest to end used and reinserted?"}
Maybe this syntax is useful in your case:
emoti_convert = { ":)" => "happily", ":(" => "sadly" }
re_emoti = Regexp.union(emoti_convert.keys)
str = "It does not work :(. Oh, it does :)!"
p str.gsub(re_emoti, emoti_convert)
#=> "It does not work sadly. Oh, it does happily!"
But if you are trying to define a grammar, this is not the way to go (agreeing with #Dave Newton's comments).

Resources