Unexpected Result When Using Fibers - ruby

I wrote a simple ruby fiber program to see how they work and got an unexpected result.
#! /usr/bin/env ruby
#encoding: utf-8
#frozen_string_literal: true
sg = Fiber.new do
File.open(begin print "Enter filename: "; gets.chomp end).each{|l| Fiber.yield l}
end
begin
loop do
puts sg.resume
end
rescue => err
puts "Error: #{err.message}"
end
datafile
This is the first
This is the second
This is the third
This is the fourth
This is the fifth
And the output from the above program
Enter filename: datafile
This is the first
This is the second
This is the third
This is the fourth
This is the fifth
#<File:0x0000557ce26ce3c8>
Error: attempt to resume a terminated fiber
I'm not sure why its displaying #File:0x0000557ce26ce3c8 in the output.
Note: ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux-gnu]

From the docs,
Upon yielding or termination the Fiber returns the value of the last executed expression
In addition to Fiber.yield calls, a fiber (like an ordinary function) returns the result of the final expression in the fiber.
The body of your fiber is this.
File.open(begin print "Enter filename: "; gets.chomp end).each{|l| Fiber.yield l}
Inside the .each, you yield each line of the file, which gets printed out as you've already observed. But then, when the fiber is done, it yields a final value, which is the result of File.open. And File.open returns the File object itself. So your sg.resume actually sees six results, not five.
"This is the first"
"This is the second"
"This is the third"
"This is the fourth"
"This is the fifth"
(the file object itself)
This actually points out a small issue in your program to begin with: You never close the file. You can do that either with File#close or by passing a block to File::open. In order to be completely safe, your fiber code should probably look like this.
sg = Fiber.new do
print "Enter filename: "
filename = gets.chomp
# By passing File.open a block, the file is closed after the block automatically.
File.open(filename) do |f|
f.each{|l| Fiber.yield l}
end
# Our fiber has to return something at the end, so let's just return nil
nil
end
begin
loop do
# Get the value. If it's nil, then we're done; we can break out of the loop.
# Otherwise, print it
value = sg.resume
break if value.nil?
puts value
end
rescue => err
puts "Error: #{err.message}"
end
Now, in addition to dealing with that pesky file handle, we have a way to detect when the fiber is done and we no longer get the "attempt to resume a terminated fiber" error.

Related

Why does defined input variable return nil after exception occurs?

I've noticed this strange behavior with the begin/rescue block in Ruby, when I define a variable, and an exception occurs and I try to call that variable that the exception occurred on it returns nil.
For example:
begin
print "Enter a number: "
input = Integer(gets.chomp)
sum = input + 5
puts "This is your number plus five: #{sum}"
rescue ArgumentError
puts "#{input}" #This outputs nil
end
Why does the begin/rescue block work like this, and is there a way to print the variable without it returning nil?
I'm not sure this is what you want but I try
input = gets.chomp
begin
number = Integer(input)
puts "your number plus five: #{number + 5}"
rescue ArgumentError
puts "#{input} is not a valid number"
end

include works with if but not else

I doing a Ruby botcamp. I'm supposed to write code that replaces all user input of 's' with 'th' so it reads like Daffy Duck is speaking. If I enter an s it will be replaced with th. That works! But If I don't enter an 's' it's supposed to print that none were included in my elsif statemnt. Instead I'm getting the error 'undefined method `include?' for nil:NilClass'. Other than that error, the interpretor is telling me the code is good.
print "Input a string: "
user_input=gets.chomp.downcase!
if user_input.include?"s"
user_input.gsub!(/s/, "th")
puts "Your string is #{user_input}!"
elsif
puts "There are no s's in your string!"
end
Any ideas on what I need to change?
You need to be careful with built-in ruby methods that end with an exclamation point (!). A lot of them will return nil if no changes were made:
'test'.downcase! # => nil
'Test'.downcase! # => "test"
Since you are assigning the result to a variable, there's no need to use the exclamation point method, since those modify in-place, you can just use the normal downcase method.
'test'.downcase # => "test"
You also later on have an elsif with no condition, that should probably just be an else. It's actually executing the first line of the "body" of the elsif as the conditional:
if false
puts "a"
elsif
puts "b" # recall that `puts` returns `nil`
puts "c"
else
puts "d"
end
This results in
b
d
being output

I'm trying to design a simple Ruby calculator and I'm getting an error

So I've been messing around with Ruby for the first time after finishing the codecademy course up to "Object Oriented Programming, Part I" and I decided to start making a calculator. For some reason though, I get this error:
calc.rb:13:in `addition': undefined local variable or method `user_input' for main:Object (NameError)
from calc.rb:21:in `<main>'
I'm confused why it doesn't see my "user_input" array. Is it out of the scope of the method? Did I initialize it wrong?
Here's the code so you can see for yourself, it's obviously nothing sophisticated and it's not finished. I'm just trying to test for addition right now.
#!/usr/bin/env ruby
user_input = Array.new
puts "Would you like to [a]dd, [s]ubtract, [m]ultiply, or [d]ivide? "
type_of_math = gets.chomp
def addition
operator = :+
puts "Please enter the numbers you want to add (enter \"=\" to stop adding numbers): "
until gets.chomp == "="
user_input << gets.chomp.to_i
end
sum = user_input.inject(operator)
return sum
end
case type_of_math
when "a"
addition
when "s"
puts "Test for subtraction"
when "m"
puts "Test for multiplication"
when "d"
puts "Test for division"
else
puts "Wrong"
end
Consider this untested variation on your code. It's more idiomatic:
def addition
user_input = []
puts 'Please enter the numbers you want to add (enter "=" to stop adding numbers): '
loop do
input = gets.chomp
break if input == '='
user_input << input
end
user_input.map(&:to_i).inject(:+)
end
Notice that it puts user_input into the method. It also uses the normal [] direct assignment of an empty array to initialize it. Rather than chomp.to_i each value as it's entered it waits to do that until after the loop exits.
Instead of while loops, consider using loop do. They tend to be more easily seen when scanning code.
Also notice there's no return at the end of the method. Ruby automatically returns the last value seen.

Why is include? throwing an argument error?

This is a snippet from a larger chunk of code:
print "> "
$next_move = gets.chomp
case $next_move.include?
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
Everytime I run it in the terminal, whether I'm using ruby 1.8.7, 1.9.3, or 2.0.0, I get the following error:
test.rb:4:in `include?': wrong number of arguments (0 for 1) (ArgumentError)
from test.rb:4
This code worked last night on a different computer.
Isn't include? checking the contents of that global variable? What other argument should I be passing to it?
I'm kinda stumped here, especially since all I did was move the code from one computer to another.
http://www.ruby-doc.org/core-1.9.3/String.html#method-i-include-3F
Returns true if str contains the given string or character.
That means it requires exactly 1 argument, so no wonder it throws ArgumentError when called without arguments.
So the code should be:
if $next_move.include? 'instructions'
puts '$next_move is instructions'
else
puts '$next move is NOT instructions'
end
Something had to have changed between the two computers you tested this on. If you are wanting to use this as a case statement, you probably had something along the lines of:
next_move = 'instructions'
case next_move
when "instructions"
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
This specifically tests if next_move IS instructions. As an if/else statement:
if next_move.include? 'instructions'
puts "$next_move is instructions"
else
puts "$next_move is NOT instructions"
end
See eval.in for more information.

private method `chomp' called for nil:NilClass (NoMethodError)

I am attempting to learn Ruby by converting a Java program to Ruby, but I've been coming up with an error surrounding this block of code:
def create
#user_input = String.new()
# #word_arr = Array.new
print "Enter the text to be converted to pig latin, EOF to quit: "
while gets do
STDOUT.flush
#user_input = gets.chomp
#word_arr = #user_input.string.split(' ')
#word_arr.each { |x| puts x.engToLatin() + ' '}
print "EOF to Quit"
#user_input = ""
end
end
I've been getting this error:
EnglishToPigLatin.rb:14:in `create': private method `chomp' called for nil:NilClass (NoMethodError)
from EnglishToPigLatin.rb:60
This is the area around line 60:
#if __FILE__ == $0
mg = EnglishToPigLatin.new
mg.create
#end
Essentially what I am trying to do is while there is still input, get that input, split it up into individual words, and run each word through a Pig Latin conversion method.
It looks like you're trying to get input inside of your loop.
Try
loop do
user_input = gets.chomp!
word_arr = user_input.to_s.split(' ')
word_arr.each { |x| puts x.engToLatin() + ' '}
puts "EOF to Quit"
end
Otherwise you're trying to get the next line of input when there isn't one. Additionally, do isn't necessary for a while statement.
You also don't need to reset #user_input to ''.
And since this is all in a block, you don't need to use instance variables, unless the methods you call need them.
Also your conditional is always true. gets will block until it gets a line of input. You can use loop for an infinite loop that ends on an interrupt.
Also, you needn't flush STDOUT if you use a puts for the last line there instead of a print.
The whole thing could be a script or a method in a module. An instance doesn't even need to be made. And if you do, instead of using two lines with your mg.create, you should define an initialize method. This is used as a constructor then, and whatever you set when you create an instance should be put there.
It can all be done like this:
loop do
puts gets.chomp.split(' ').map{ |x| x.engToLatin() }.join(' ')
puts "EOF to Quit"
end
Mario's answer is right. But I have the following notes.
You can still use the while construction as below.
+' ' implies that you don't want line breaks after each word. I changed that part. map and join is common in similar cases. print does not add a line break while puts does.
I am not sure what you are trying to do with STDOUT.flush. If you wanted to scroll to the top of the screen before each output, use system('clear').
You have a method entToLatin, and it should work, but it is a ruby convention to use underscore, like eng_to_latin for methods (although there are a few exceptions).
So a more rubyish way would be:
def create
print "Enter the text to be converted to pig latin, EOF to quit: "
while input = gets.strip and input != 'EOF'
system('clear')
puts input.split(/\s+/).map{|x| x.engToLatin}.join(' ')
puts "EOP to Quit"
end
end
And if you are using ruby 1.9.2, you can shorten map so that:
def create
print "Enter the text to be converted to pig latin, EOF to quit: "
while input = gets.strip and input != 'EOF'
system('clear')
puts input.split(/\s+/).map(:engToLatin).join(' ')
puts "EOP to Quit"
end
end

Resources