Is there a way to implicitly call methods on the object of a case statement?
IE:
class Foo
def bar
1
end
def baz
...
end
end
What I'd like to be able to do is something like this...
foo = Foo.new
case foo
when .bar==1 then "something"
when .bar==2 then "something else"
when .baz==3 then "another thing"
end
... where the "when" statements are evaluating the return of methods on the case object. Is some structure like this possible? I haven't been able to figure out the syntax if so...
FWIW, you don't need to pass an object to a case statement in 1.8.7 at all.
foo = Foo.new()
case
when foo.bar == this then that
when foo.baz == this then that
end
I was surprised as hegg.
http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-what-you-can-do-with-it/
What case .. when does is it calls the method === on your when values, passing your foo object as the argument to the === method. So in this code:
case foo
when 1 then "something"
when 2 then "something else"
when 3 then "another thing"
end
It will try 1 === foo, then 2 === foo, then 3 === foo, until one of them returns a truthy value.
One way of making case .. when more powerful is using Procs as the when values. I'm not sure about earlier versions of Ruby, but in 1.9, proc === x is equivalent to proc.call(x). So you can write code like this:
case foo
when Proc.new { foo.bar == 1 } then "something"
when Proc.new { foo.bar == 2 } then "something else"
when Proc.new { foo.baz == 3 } then "another thing"
end
Note that we don't even have to pass foo into the Procs, since we already have access to it. I don't think this is a very good choice of control structure for this example, a simple chain of ifs would make more sense:
if foo.bar == 1
"something"
elsif foo.bar == 2
"something else"
elsif foo.baz == 3
"another thing"
end
For different scenario where you want to test truthy method value of an object
class Foo
def approved?
false
end
def pending?
true
end
end
foo = Foo.new
case foo
when :approved?.to_proc
puts 'Green'
when :pending?.to_proc
puts 'Amber'
else
puts 'Grey'
end
# This will output: "Amber"
It looks like you're wanting to change the default receiver. This is hacky, but you could do something like:
string = Foo.new.instance_eval do
if bar==1 then "something"
elsif bar==2 then "something else"
elsif baz==3 then "another thing"
end
end
That's a big, terrible code smell, though, if you're just doing it because you're lazy. If you're doing it because you're creating a DSL, that's something else.
Related
Lets say you are using the if syntax for Ruby that goes like this:
if foo == 3: puts "foo"
elsif foo == 5: puts "foobar"
else puts "bar"
end
Is there a way to do it so Ruby executes two things in the if statement, say, like this:
if foo == 3
puts "foo"
foo = 1
elsif foo = ...
How can I make it so I can put two statements in using the first syntax?
if foo == 3: puts "foo"; puts "baz"
elsif foo == 5: puts "foobar"
else puts "bar"
end
However, I advise against it.
Ruby permits semicolons to separate statements on a single line, so you can do:
if foo == 3: puts "foo"; puts "bar"
elsif foo == 5: puts "foobar"
else puts "bar"
end
To be tidy, I would probably terminate both statements with ;:
if foo == 3: puts "foo"; puts "bar";
elsif foo == 5: puts "foobar"
else puts "bar"
end
Unless you have an excellent reason though, I wouldn't do this, due to its effect on readability. A normal if block with multiple statements is much easier to read.
I think case statements look far better than the if statements:
case foo
when 3
puts "foo"
when 5
puts "foobar"
else
puts "bar"
end
And this allows for multiple statements per condition.
I can't figure out how to assign a function call to a ruby hash.
What I want to do is to assign a function to a hash key, and later
call this function using the classic hash lookout syntax.
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => Foo() }
puts "Executing"
test["foo"]
This code fails, function Foo is called after puts "Assign", during hash creation, and nothing happens after puts "Executing"
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => Foo }
puts "Executing"
test["foo"]
with this code I receive an uninitialized constant Foo (NameError).
Finally with
def Foo()
puts "bar"
end
puts "Assigning"
test = { "foo" => :Foo }
puts "Executing"
test["foo"]
I get not outputs.
Any suggestions?
Thanks to all for answres and suggestions.
What I'm going to do is to test
if a hash based approach to call function is faster than
an equivalent code based on if / case statements.
funcs["foo"].call
fatser than
if func_name == "foo" then
Foo()
elsif ...
...
end
or
case func_name
when "foo"
Foo()
when ...
...
end
Obviously for a big number of functions (~150) and hundreds of
calling cycles
you could use lambda's instead of methods. Two options here:
hash = {:foo => lambda { puts 'bar } }
hash[:foo].call
the second (more complicated) is this:
irb(main):001:0> class Hash
irb(main):002:1> alias :orig_anc :'[]'
irb(main):003:1>
irb(main):004:1* def [](key)
irb(main):005:2> if orig_anc(key).is_a? Proc
irb(main):006:3> orig_anc(key).call
irb(main):007:3> else
irb(main):008:3* orig_anc(key)
irb(main):009:3> end
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> h = {:hello => 'world', :foo => lambda { puts 'bar' }}
=> {:hello=>"world", :foo=>#<Proc:0x843224c#(irb):12 (lambda)>}
irb(main):013:0> h[:hello]
=> "world"
irb(main):014:0> h[:foo]
bar
=> nil
irb(main):015:0>
The second one just allows you to skip using 'call' method
There is no easy possibility to make your function execute simply by retrieving the hash key withput overriding Hash's [] method, as Vlad pointed out, i.e.
def foo
puts "hi"
end
... # magic
test["foo"] # outputs hi
won't work. What you can do, though, is assign the method reference using Object#method and then invoke it using call:
def foo
puts "hi"
end
test = { "foo" => method(:foo) }
test["foo"].call # => hi
First things first, start method names with lower case letters. Then, moving to first example, Ruby is eager so test = { "foo" => Foo() } Foo is called.
In the second example, variables starting with an uppercase letter are considered constants, so Ruby looks for one and do not bother looking for a method. Take into account that if foo where a method then foo would be called as in the first example.
Third example: test["foo"] returns :Foo, a symbol. Nowhere in your code Foo() is invoked.
*Suggestions
1.- Take a look at identifiers nomenclature in Ruby.
2.- You may took a look at lambda, Proc and Object#method
3.- If you have some money to spare and don't mind buying a pair of books, give Programming Ruby and Metaprogramming in Ruby a chance (both can be bought in PragProg.
You could try using the hash constructor with a block, so
def foo
puts "hi"
end
test = Hash.new do |hash, key|
send(key) if respond_to?(key)
end
test["foo"] # prints "hi", returns nil
Note, this is a follow up to my question here.
I'm trying to parse the following Tcl code:
foo bar {
biz buzz
}
In Tcl, foo is the method name, bar is the argument, and the rest is a "block" to be processed by eval.
Now here is my current implementation to this:
def self.foo(name, &block)
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
meth.to_s &block
end
tcl = <<-TCL.gsub(/^\s+/, "").chop
foo bar {
biz buzz
}
TCL
instance_eval(tcl)
Which outputs the following:
:bar
#<Proc:0x9e39c80#(eval):1>
Foo --> bar
block does not exist
In this example, when the block is passed up to the foo method, it does not exist. Yet in method_missing it does exist (at least it appears to exist). What's going on here?
Note, I am aware of ruby's precedence of parentheses and realize this works:
foo (bar) {
biz buzz
}
However, I want to have the parentheses omitted. So is this possible in ruby (without lexical analysis)?
You can do (I marked the lines I changed):
def self.foo args # changed
name, block = *args # changed
puts "Foo --> #{name}"
if block
puts "block exists"
else
puts "block does not exist"
end
end
def self.method_missing(meth, *args, &block)
p meth
p block
return meth.to_s, block # changed
end
That way, block will exist.
This has nothing to do with method_missing. You simply can't omit parentheses when passing block along with some parameters. In your case, Ruby will try to call bar method with block as an argument, and the result of it all will be passed to foo method as a single argument.
You can try this yourself by simplifying the method call (all the metaprogramming just obscures the real problem in your case):
# make a method which would take anything
def a *args, &block
end
# try to call it both with argument and a block:
a 3 {
4
}
#=>SyntaxError: (irb):16: syntax error, unexpected '{', expecting $end
# from /usr/bin/irb:12:in `<main>'
So the best solution I've found is to just gsub the string before processing it.
tcl = <<-TCL.gsub(/^\s+/, "").chop.gsub('{', 'do').gsub('}', 'end')
foo bar {
biz buzz
}
TCL
Consider the following test for rspec:
class RspecTest
def initialize
end
def to_s
"foo"
end
end
describe RspecTest do
it "should return foo (to_s)" do
RspecTest.new.should == "foo"
end
it "should return foo (inspect)" do
RspecTest.new.inspect == "foo"
end
end
And when tested through rspec:
%: rspec rspec_test.rb
F.
Failures:
1) RspecTest should return foo (to_s)
Failure/Error: RspecTest.new.should == "foo"
expected: "foo"
got: foo (using ==)
Diff:
# ./rspec_test.rb:13:in `block (2 levels) in <top (required)>'
Finished in 0.00059 seconds
2 examples, 1 failure
So the first test fails, whereas the second test passes. Why is that?
The second test passes, because it doesn't test anything. It doesn't contain any expectation (i.e. a call to should or should_not). It cannot fail, because there is nothing to fail.
The first test fails, because you are asserting that an instance of RspecTest is equal to the string 'foo'. This cannot possibly be true. How could those two objects possibly be equal if they aren't even the same kind of object?
Judging by the description of the test, you didn't actually mean to test whether the instance of RspecTest is equal to the string 'foo', but rather whether the return value of the instance method to_s is equal to the string 'foo'. However, you never call to_s anywhere.
Let's first fix the two obvious problems. Now, we have a test like this:
it 'should return foo (to_s)' do
RspecTest.new.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
RspecTest.new.inspect.should == 'foo'
end
There is some unnecessary duplication there with the two RspecTest.new calls, so let's fix that by simply making RspecTest.new the default subject:
subject { RspecTest.new }
it 'should return foo (to_s)' do
subject.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
subject.inspect.should == 'foo'
end
And actually, if you don't supply an explicit subject, then RSpec will walk up the chain of nested describe blocks until it finds a class, and will simply call that class's new method to provide the subject. So, we can just delete the subject declaration:
it 'should return foo (to_s)' do
subject.to_s.should == 'foo'
end
it 'should return foo (inspect)' do
subject.inspect.should == 'foo'
end
Personally, I prefer to let RSpec provide the example name by itself, so that the example name and the actual example code don't get out of sync, so I'd probably write that more like this:
describe RspecTest do
describe '#to_s' do
it { subject.to_s.should == 'foo' }
end
describe '#inspect' do
it { subject.inspect.should == "foo" }
end
end
Which yields:
RspecTest
#to_s
should == "foo"
#inspect
should == "foo"
Finished in 0.16023 seconds
2 examples, 0 failures
Last but not least, your initializer isn't actually doing anything, so you don't need it. All together, my version looks like this:
class RspecTest
def to_s; 'foo' end
end
describe RspecTest do
describe '#to_s' do
it { subject.to_s.should == 'foo' }
end
describe '#inspect' do
it { subject.inspect.should == "foo" }
end
end
I think your test should be the following (and they'll both pass). The first one is missing the actual to_s call, and the seocnd one is missing the .should:
describe RspecTest do
it "should return foo (to_s)" do
RspecTest.new.to_s.should == "foo"
end
it "should return foo (inspect)" do
RspecTest.new.inspect.should == "foo"
end
end
I have a class that I want to compare to both strings and symbols in a case statement, so I thought that I just override the ===() method for my class and all would be gold. However my ===() method never gets called during the case statement. Any ideas?
Here is some example code, and what happens in a irb session:
class A
def initialize(x)
#x=x #note this isn't even required for this example
end
def ===(other)
puts "in ==="
return true
end
end
irb(main):010:0> a=A.new("hi")
=> #
irb(main):011:0> case a
irb(main):012:1> when "hi" then 1
irb(main):013:1> else 2
irb(main):014:1> end
=> 2
(it never prints the message and should always return true anyway)
Note that ideally I'd like to do a
def ===(other)
#puts "in ==="
return #x.===(other)
end
Thanks in advance.
The expression after the 'case' keyword is the right hand side of the === expression, and the expression after the 'when' keyword is on the left hand side of the expression. So, the method that is being called is String.===, not A.===.
A quick approach to reversing the comparison:
class Revcomp
def initialize(obj)
#obj = obj
end
def ===(other)
other === #obj
end
def self.rev(obj)
Revcomp.new(obj)
end
end
class Test
def ===(other)
puts "here"
end
end
t = Test.new
case t
when Revcomp.rev("abc")
puts "there"
else
puts "somewhere"
end