Ruby proc vs lambda in initialize() - ruby

I found out this morning that proc.new works in a class initialize method, but not lambda. Concretely, I mean:
class TestClass
attr_reader :proc, :lambda
def initialize
#proc = Proc.new {puts "Hello from Proc"}
#lambda = lambda {puts "Hello from lambda"}
end
end
c = TestClass.new
c.proc.call
c.lambda.call
In the above case, the result will be:
Hello from Proc
test.rb:14:in `<main>': undefined method `call' for nil:NilClass (NoMethodError)
Why is that?
Thanks!

The fact that you have defined an attr_accessor called lambda is hiding the original lambda method that creates a block (so your code is effectively hiding Ruby's lambda). You need to name the attribute something else for it to work:
class TestClass
attr_reader :proc, :_lambda
def initialize
#proc = Proc.new {puts "Hello from Proc"}
#_lambda = lambda {puts "Hello from lambda"}
end
end
c = TestClass.new
c.proc.call
c._lambda.call

Related

How do I access class methods in a define_singleton_method block in Ruby

I am trying to access class methods within a define_singleton_method block, but it doesn't seem to be working.
Here is an example.
class Test
attr_accessor :tags
def initialize
#tags = []
#tags.define_singleton_method(:<<) do |val|
val = tosymbol(val)
push(val)
end
end
def tosymbol(value)
value = value.to_s
value = value.gsub!(/\s+/,'_') || value
value = value.downcase! || value
return value.to_sym
end
end
But when I use it I get an error.
test = Test.new
test.tags<<"Hello World"
NoMethodError: undefined method `tosymbol' for []:Array
from /home/joebloggs/GitHub/repo/file.rb:183:in `block in initialize'
from (irb):9
from /home/joebloggs/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
I tried changing val = tosymbol(val) to val = Test::tosymbol(val) but that didn't work either, I get undefined method 'tosymbol' for Test:Class
I could re-write what tosymbol is doing, but it wouldn't be very DRY. Where am I going wrong? Thanks.
Where am I going wrong?
You're (re)defining a << method for instance of Array class, not Test class.
While doing so you are trying to access tosymbol method, that is not defined in Array class, but in Test class.
What you want, probably (read judging by your code sample), is to define << method for instances of Test class:
def initialize
#tags = []
end
def <<(val)
tags << tosymbol(val)
end
test = Test.new
test << "Hello World"
#=> [:hello_world]
EDIT
To make your example to work you just need to assign the instance to a variable and call the tosymbol method with correct receiver:
def initialize
#tags = []
test = self # <============
#tags.define_singleton_method(:<<) do |val|
val = test.tosymbol(val)
push(val)
end
end
Now:
test.tags << 'Hello World'
#=> [:hello_world]

How do I change the context of lambda?

If you run the code below you get an error.
class C
def self.filter_clause param_1
puts param_1
yield # context of this param is class B
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
The error is
NameError: undefined local variable or method `hi' for B:Class
from (pry):17:in `block in <class:B>'
From my understanding the reason for this is the lambda is running under the context of class B. So it can't find the method def hi. I can't figure out how to get it to run under the context of class C.
Essentially I want to be able to inject a method being called in another class that accepts an argument and a block.
For example:
filter_clause("value", ->{ hi })
Is this possible?
Not sure if I am making sense.
You're close. If you want the lambda(/block, really) to execute in the context of an instance of C (since hi is an instance method on C), then you'll need to instantiate it and then instance_exec the block on that new instance:
class C
def self.filter_clause param_1, &block
puts new.instance_exec &block
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
# => hello
Are you opposed to passing the context into the block? If not, something like this would work:
class C
def self.filter_clause param_1
puts param_1
yield self
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->(klass){ klass.new.hi }) # "hello"
end

Undefined method `lambda' in ruby code

I have a code
class A < BasicObject
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
a = A.new :lambda, 123 do |var|
puts "ha ha ha"
end
why does it cause an error?
undefined method `lambda' for #<A:0x00000001687968> (NoMethodError)
unlike this one (it doesn't cause it)
class A
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
The lambda method is defined in the Kernel module. Object includes Kernel. BasicObject does not. So if you want to use lambda from a BasicObject, you have to call it as ::Kernel.lambda.
Note that this is not specific to lambda - it applies to any other Kernel method (like e.g. puts) as well.
PS: Note that #var1 = lambda &block does the same thing as just writing #var1 = block, so the use of lambda isn't actually necessary here.
You are using BasicObject as base class which is an explicit Blank class and specifically does not include Kernel, so you need the qualifier ::Kernel when you access any Kernel method.
On a separate note -
Instead of passing an argument that you have a block you can use the Kernel method block_given?
So taking your example -
class A
def initialize *args, &block
if block_given?
#var1 = lambda &block
end
puts #var1.call
end
end
a = A.new 123 do |var|
puts "ha ha ha"
end

Create a Mixin with a set of methods than can call to another class methods

I want to define a set of methods that can be added to a class (C in the example) using a Mixin. These methods can be defined by any class that inherits from another class (A in the example) and should be able to call methods in the receiver instance (C instance).
I have this snippet of code:
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).to_proc
M.send(:define_method, method) do
yield block.call
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
Adding methods using blocks works, but last line fails :(
foo+c_method
test.rb:28:in `bar': undefined local variable or method `c_method' for B:Class (NameError)
from test.rb:15:in `call'
from test.rb:15:in `block in init'
from test.rb:46:in `<main>'
What I'm doing wrong? Or this makes no sense?
Thanks
Juan
When you prepare instance_exec &Proc.new inside if statement, this statement is executed within instance of C class as context. You can verify this by adding puts self inside block for init(:foo).
On the other hand, when you call yield block.call you yield thread execution into context of B class object (not to instance of this class, of course :) ). This place of your code doesn't know anything about C::c_method and this is cause of error.
It seems that what I'm trying to do is to unbind the method :bar from B and bind to C, what it's not allowed. You can find more info in this great post
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).unbind
M.send(:define_method, method) do
m = block.bind(self)
puts m
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
foo+c_method
test.rb:16:in `bind': singleton method called for a different object (TypeError)
from test.rb:16:in `block in init'
from test.rb:48:in `<main>'

ScopeGates in Ruby

I am reading Metaprogramming in Ruby book. In that book, when I was reading about scopegates, the following code was shown
my_var = "Success"
MyClass = Class.new do
puts "#{my_var} in the class definition"
define_method :my_method do
puts "#{my_var} in the method"
end
end
MyClass.new.my_method
=>Success in the class definition
Success in the method
Now when I do the execute in the following, I get an error saying undefined method my_method
MyClass.new
MyClass:0x00000100936a30
MyClass.my_method
NoMethodError: undefined method `my_method' for MyClass:Class
from (irb):11
from /usr/local/bin/irb:12:in `<main>'
Why my_method gets created, when it gets called as MyClass.new.my_method and not MyClass.my_method?
The define_method method adds an instance method to a class. When you call define_method in your anonymous class, your method is being added as an instance method. To add it as a class method, you have to add it to the metaclass.
The easiest way to do that is with the class << self syntax.
MyClass = Class.new do
class << self
define_method(:my_method) do
puts "class method"
end
end
end
MyClass.my_method # => "class method"

Resources