Need help understanding a particular Ruby block - ruby

I introduced the bug shown below into my code the other day and wanted to better understand the implications.
File.open('test.txt').readlines do |line|
puts "#{line} test"
end
File.open.readlines does exactly as expected and returns an array containing all of the lines in the file. But immediately following that array is a block. The array returned by readlines calls no member methods, each for instance. I'm assuming the block has nothing calling it, therefore it does nothing. In other languages this may have thrown a compiler warning or in C the block would be considered a nested scope and it would execute. But as shown, Ruby (1.9.2) happily runs with no errors, and no output generated by puts.
For completeness here's the corrected version.
File.open('test.txt').each_line do |line|
puts "#{line} test"
end
I would like to understand the behavior of the first example. Is my assumption correct in that the block is essentially anonymous and didn't execute because nothing called it?

Here is what is happening in your particular example. When you call
File.open('test.txt').readlines do |line|
puts "#{line} test"
end
The block is being passed to the readlines method. The block is not passed to the array that is returned by the readlines method. This is a valid case and thus the ruby interpreter does not complain.
Here is a simpler scenario to illustrate this
Let's say I had a class defined as follows
class Foo
def bar
end
end
Now if I call
Foo.new.bar {puts "hello"}
There will be no error from the interpreter and the "hello" will not be printed.
If I yield to the block in bar as follows
class Foo
def bar
yield
end
end
Then the puts will be executed and you should see hello printed
Foo.new.bar {puts "hello"} # prints hello
In summary blocks are passed to methods not objects and you can pass a block to any method in ruby even one which does not expect it.

Related

Functional programming with Ruby

Ruby has support for functional programming features like code blocks and higher-level functions (see Array#map, inject, & select).
How can I write functional code in Ruby?
Would appreciate examples like implementing a callback.
You could use yield
def method(foo,bar)
operation=foo+bar
yield operation
end
then you call it like this:
foo=1
bar=2
method(foo,bar) {|result| puts "the result of the operation using arguments #{foo} and #{bar} is #{result}"}
the code in the block (a block is basically "a chunk of code" paraphrasing ruby programmers) gets executed in the "yield operation" line, you pass the method a block of code to be executed inside the method defined. This makes Ruby pretty versatile language.
In this case yield receives an argument called "operation". I wrote it that way because you asked for a way to implement a callback.
but you could just wrote
def method()
puts "I'm inside the method"
yield
end
method(){puts "I'm inside a block"}
and it would output
I'm inside the method
I'm inside a block

override namespaced puts only works after overriding Kernel.puts?

Sorry for the vague question title, but I have no clue what causes the following:
module Capistrano
class Configuration
def puts string
::Kernel.puts 'test'
end
end
end
Now when Capistrano calls puts, I don't see "test", but I see the original output.
However, when I also add this:
module Kernel
def puts string
::Kernel.puts 'what gives?'
end
end
Now, suddenly, puts actually returns "test", not "what gives?", not the original content, but "test".
Is there a reasonable explanation why this is happening (besides my limited understanding of the inner-workings of Ruby Kernel)?
Things that look off to me (but somehow "seem to work"):
I would expect the first block to return 'test', but it didn't
I would expect the combination of the two blocks to return 'what gives?', but it returns 'test'?
The way I override the Kernel.puts seems like a never-ending loop to me?
module Capistrano
class Configuration
def puts string
::Kernel.puts 'test'
end
def an_thing
puts "foo"
end
end
end
Capistrano::Configuration.new.an_thing
gives the output:
test
The second version also gives the same output. The reason is that you're defining an instance level method rather than a class level method (this post seems to do a good job explaining the differences). A slightly different version:
module Kernel
def self.puts string
::Kernel.puts 'what gives?'
end
end
does the following. Because it is causing infinite recursion, like you expected.
/tmp/foo.rb:14:in `puts': stack level too deep (SystemStackError)
from /tmp/foo.rb:14:in `puts'
from /tmp/foo.rb:4:in `puts'
from /tmp/foo.rb:7:in `an_thing'
from /tmp/foo.rb:18
shell returned 1
I use an answer rather than a comment because of its editing capabilities. You can edit it to add more information and I may delete it later.
Now when Capistrano calls puts, I don't see "test", but I see the
original output.
It's difficult to answer your question without seeing how Capistrano calls puts and which one. I would say it's normal if puts displays its parameter, using the original Kernel#puts (it is not clear what you call original output, I must suppose you mean the string given to puts).
I would expect the first block to return 'test', but it didn't
The only way I see to call the instance method puts defined in the class Configuration in the module Capistrano is :
Capistrano::Configuration.new.puts 'xxx'
or
my_inst_var = Capistrano::Configuration.new
and somewhere else
my_inst_var.puts 'xxx'
and of course it prints test. Again, without seeing the puts statement whose result surprises you, it's impossible to tell what's going on.
I would expect the combination of the two blocks to return 'what gives?', but it returns 'test'?
The second point is mysterious and I need to see the code calling puts, as well as the console output.

RSpec test for a module

I'm brand new to RSpec and TDD. I was wondering if someone might help me with creating a test well-suited for this Module:
module Kernel
# define new 'puts' which which appends "This will be appended!" to all puts output
def puts_with_append *args
puts_without_append args.map{|a| a + "This will be appended!"}
end
# back up name of old puts
alias_method :puts_without_append, :puts
# now set our version as new puts
alias_method :puts, :puts_with_append
end
I'd like for my test to check that the content from a 'puts' ends with "This will be appended!". Would that be a sufficient test? How would I do that?
The best tests test what you're trying to achieve, not how you achieve it... Tying tests to implementation makes your tests brittle.
So, what you're trying to achieve with this method is a change to "puts" whenever your extension is loaded. Testing the method puts_with_append doesn't achieve this goal... If you later accidentally re-alias that to something else, your desired puts change won't work.
However, testing this without using an implementation detail would be rather difficult, so instead, we can try to push the implementation details down to somewhere they won't change, like STDOUT.
Just the Test Content
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
Full Test
I'm going to turn this into a blog post within the next day or so, but I think you should also consider that you've got a desired result for one and many arguments, and your tests should be easy to read. The ultimate structure I'd use is:
require "rspec"
require "./your_extention.rb"
describe Kernel do
describe "#puts (overridden)" do
context "with one argument" do
it "should append the appropriate string" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
puts "One"
end
end
context "with more then one argument" do
it "should append the appropriate string to every arg" do
$stdout.stub!(:write)
$stdout.should_receive(:write).with("OneThis will be appended!")
$stdout.should_receive(:write).with("TwoThis will be appended!")
puts("One", "Two")
end
end
end
end

ruby Array.inspect vs. Array[element].to_s

I'm working with an array, which we'll call books, of complex objects, which we'll call Book. The problem is when I call puts "#{books.inspect}", ruby outputs a stream of binary (unreadable utf8 characters). However, when I call puts #{books[0].to_str}", I get a brief, pretty output that describes the book in question. Not sure if it is relevant, but Book is a subclass (we can call it's parent class Item), and books.length=1
Ruby implies that .to_s and .inspect are synonymous, but they are clearly providing different results in practice. Does anyone know why this is happening and can you provide a suggestion for how to get the nice output I want out of the whole books collection?
Misc info:
[chk ~ ]$ ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux]
class Myclass
def to_s
'my string representation'
end
def inspect
'my inspection'
end
end
a= [Myclass.new]
p a
puts a
outputs ::
[my inspection]
my string representation
The inspect method is called for each element inside the array. If that method is not defined you get the default class representation. You will just need to define inspect.
You could always just do :
def inspect
self.to_s
end
books.inspect and books[0].to_s are absolutely NOT the same.
The first is a call to inspect method of book object, which is an array. The second is a call to to_s method of books[0] object, whatever it is that is contained inside the array.
As you didn't specify what exactly is books[0], I'm afraid I can't tell anything more.
books is an array so calling books.inspect is calling Array#inspect. That method works by calling .inspect on the elements, so in this case Book#inspect.
Doing string-interpolation "Here's some info #{value}" calls .to_s on the value object whatever its class may be.
Putting these together as an example "Books #{books}" would call Array#to_s (which is an alias for Array#inspect) that calls Book#inspect to produce the output string.
When you want things to work consistently, you should define inspect and make to_s an alias of inspect as is done in the Array class as an example. e.g.
class MyClass
def inspect
'my string representation'
end
alias_method :to_s, :inspect
end
There may be times when you want them to be different where .inspect is useful for debugging and .to_s for more usual output. In that case you would define each of them with different code.

Optional parens in Ruby for method with uppercase start letter?

I just started out using IronRuby (but the behaviour seems consistent when I tested it in plain Ruby) for a DSL in my .NET application - and as part of this I'm defining methods to be called from the DSL via define_method.
However, I've run into an issue regarding optional parens when calling methods starting with an uppercase letter.
Given the following program:
class DemoClass
define_method :test do puts "output from test" end
define_method :Test do puts "output from Test" end
def run
puts "Calling 'test'"
test()
puts "Calling 'test'"
test
puts "Calling 'Test()'"
Test()
puts "Calling 'Test'"
Test
end
end
demo = DemoClass.new
demo.run
Running this code in a console (using plain ruby) yields the following output:
ruby .\test.rb
Calling 'test'
output from test
Calling 'test'
output from test
Calling 'Test()'
output from Test
Calling 'Test'
./test.rb:13:in `run': uninitialized constant DemoClass::Test (NameError)
from ./test.rb:19:in `<main>'
I realize that the Ruby convention is that constants start with an uppercase letter and that the general naming convention for methods in Ruby is lowercase. But the parens are really killing my DSL syntax at the moment.
Is there any way around this issue?
This is just part of Ruby's ambiguity resolution.
In Ruby, methods and variables live in different namespaces, therefore there can be methods and variables (or constants) with the same name. This means that, when using them, there needs to be some way to distinguish them. In general, that's not a problem: messages have receivers, variables don't. Messages have arguments, variables don't. Variables are assigned to, messages aren't.
The only problem is when you have no receiver, no argument and no assignment. Then, Ruby cannot tell the difference between a receiverless message send without arguments and a variable. So, it has to make up some arbitrary rules, and those rules are basically:
for an ambiguous token starting with a lowercase letter, prefer to interpret it as a message send, unless you positively know it is a variable (i.e. the parser (not(!) the interpreter) has seen an assignment before)
for an ambiguous token starting with an uppercase letter, prefer to interpret it as a constant
Note that for a message send with arguments (even if the argument list is empty), there is no ambiguity, which is why your third example works.
test(): obviously a message send, no ambiguity here
test: might be a message send or a variable; resolution rules say it is a message send
Test(): obviously a message send, no ambiguity here
self.Test: also obviously a message send, no ambiguity here
Test: might be a message send or a constant; resolution rules say it is a constant
Note that those rules are a little bit subtle, for example here:
if false
foo = 'This will never get executed'
end
foo # still this will get interpreted as a variable
The rules say that whether an ambiguous token gets interpreted as a variable or a message send is determined by the parser and not the interpreter. So, because the parser has seen foo = whatever, it tags foo as a variable, even though the code will never get executed and foo will evaluate to nil as all uninitialized variables in Ruby do.
TL;DR summary: you're SOL.
What you could do is override const_missing to translate into a message send. Something like this:
class DemoClass
def test; puts "output from test" end
def Test; puts "output from Test" end
def run
puts "Calling 'test'"
test()
puts "Calling 'test'"
test
puts "Calling 'Test()'"
Test()
puts "Calling 'Test'"
Test
end
def self.const_missing(const)
send const.downcase
end
end
demo = DemoClass.new
demo.run
Except this obviously won't work, since const_missing is defined on DemoClass and thus, when const_missing is run, self is DemoClass which means that it tries to call DemoClass.test when it should be calling DemoClass#test via demo.test.
I don't know how to easily solve this.

Resources