Is there a method_eval or similar thing ?

N

Nit Khair

I have a method called askyesno which takes a string and returns whether
the user pressed y or n. However, I now want the user to be able to pass
a block in with blocks for what to do for YES and NO.

However, in this block I need to be able to access method level
variables. Here's how i am trying to code a sample of it. btw, askyesno
will actually sit inside a module in my app.

--
# the ch here is only for testing, it contains y or n, so we can test
this out
# easily

def askyn(str, ch, &bl)
puts str
h = {}

def actionbind(key, &block)
h[key] = block
end

if block_given?
method_eval(&bl) # or module_eval etc
end

case ch
when 'y'
h[:yes].call if h.include? :yes
when 'n'
h[:no].call if h.include? :no
end
end

askyn("Do you wish to proceed?", 'n') do
actionbind:)yes) { puts "user pressed yes" }
actionbind:)no) { puts "user pressed no" }
end


Currently, i have "ask" methods that allow for many options (not just
yes/no), the selection is passed back to the caller and he has a
case-when in which he takes appropriate action.

I was considering trying out something like the above where he could
pass in proc - bindings for each key. Is there any way to access the
hash "h" from the block ?
 
S

Sandro Paganotti

[Note: parts of this message were removed to make it a legal post.]

I think that maybe you can wrap everything in a class:

class Ask
def initialize(question)
@question = question; @h={}
yield(self)
end
def actionbind(key, &block)
@h[key] = block
end
end

Ask.new("Do you wish to proceed?") do |question|
question.actionbind:)yes){ puts "user pressed yes" }
question.actionbind:)no) { puts "user pressed no" }
end



I have a method called askyesno which takes a string and returns whether
the user pressed y or n. However, I now want the user to be able to pass
a block in with blocks for what to do for YES and NO.

However, in this block I need to be able to access method level
variables. Here's how i am trying to code a sample of it. btw, askyesno
will actually sit inside a module in my app.

--
# the ch here is only for testing, it contains y or n, so we can test
this out
# easily

def askyn(str, ch, &bl)
puts str
h = {}

def actionbind(key, &block)
h[key] = block
end

if block_given?
method_eval(&bl) # or module_eval etc
end

case ch
when 'y'
h[:yes].call if h.include? :yes
when 'n'
h[:no].call if h.include? :no
end
end

askyn("Do you wish to proceed?", 'n') do
actionbind:)yes) { puts "user pressed yes" }
actionbind:)no) { puts "user pressed no" }
end


Currently, i have "ask" methods that allow for many options (not just
yes/no), the selection is passed back to the caller and he has a
case-when in which he takes appropriate action.

I was considering trying out something like the above where he could
pass in proc - bindings for each key. Is there any way to access the
hash "h" from the block ?
 
N

Nit Khair

Sandro said:
I think that maybe you can wrap everything in a class:

class Ask
def initialize(question)
@question = question; @h={}
yield(self)
end
def actionbind(key, &block)
@h[key] = block
end
end

Ask.new("Do you wish to proceed?") do |question|
question.actionbind:)yes){ puts "user pressed yes" }
question.actionbind:)no) { puts "user pressed no" }
end

Thanks, I will be using this whereever there are classes, but would like
to know, is there any way i can do it with methods. I know i could
define the hash as a global (or maybe module level object) but that
would not be a clean solution.

There is also one question I would like to ask as a ruby newbie
(struggling to grow up). Is the above approach of passing blocks to an
ask method making it more "rubyish" or better than returning the input
character and letting the user decide what to do.
 
G

Gregory Brown

There is also one question I would like to ask as a ruby newbie
(struggling to grow up). Is the above approach of passing blocks to an
ask method making it more "rubyish" or better than returning the input
character and letting the user decide what to do.

Sorry to self-promote, but you may want to use HighLine or at least
look at it for ideas:

http://highline.rubyforge.org/
 
N

Nit Khair

Gregory said:
Sorry to self-promote, but you may want to use HighLine or at least
look at it for ideas:

http://highline.rubyforge.org/
Ah. that's fine :) I incidentally have been studying *a lot* of code in
the last month, and Highline is what i did go through the other day. It
was recommended to me by someone in another thread as a project to read
to improve my ruby.

However, here I mean passing a block to a method in a more general way.
This was just an example. Thanks for the reminder, will go back and
check ...
 
B

Brian Candler

Nit said:
askyn("Do you wish to proceed?", 'n') do
actionbind:)yes) { puts "user pressed yes" }
actionbind:)no) { puts "user pressed no" }
end

As you have already realised, you need somewhere to store the bindings
between keys and actions. This could be a hash allocated by askyn:

askyn("Do you wish to proceed?", "n") do |h|
actionbind(h,:yes) { puts "user pressed yes" }
... etc

But in this case I think a wrapper object to hold the bindings would be
better, as already posted by someone else. (That's what the optparse.rb
library does, amongst others; it's a clean and well-recognised solution)

However you could turn it around and get the caller to pass a
pre-prepared hash containing all the bindings to askyn:

askyn("Do you wish to proceed?", "n",
:yes => lambda { puts "user pressed yes" }
:no => lambda { puts "user pressed no" }
)

I quite like that approach. This sort of API also makes it easy for the
user to invoke other methods in their object for each response, e.g.

askyn("Do you wish to proceed?", "n"
:yes => method:)do_yes),
:no => method:)do_no)
)

Now, as you say, you could keep your hash in a global variable, but that
would be very poor as it would make your code non-threadsafe. But it's
possible to fix this by using a thread-local variable:

def askyn(prompt, default)
Thread[:askyn] = {}
yield
.. etc
end

def actionbind(key, &block)
Thread[:askyn][key] = block
end

I'd say that smells somewhat, but it would work and it hides the hash.

Or scarily, you could attempt to create a local variable in the binding
of the caller, which is I think what you were asking for initially. This
is pretty horrible and I won't even attempt it here :) You can google
for binding_of_caller, and note that eval lets you pass in a binding.

Regards,

Brian.

P.S. Note that if you nest def within a def, as your posted code does,
it probably doesn't do what you expect. The inner def will define a new
method in your object, at the time when the outer def is called.
 
B

Brian Candler

askyn("Do you wish to proceed?", "n",
:yes => lambda { puts "user pressed yes" }
:no => lambda { puts "user pressed no" }
)

Oops, I missed a comma from the end of the second line.
 
N

Nit Khair

Brian said:
However you could turn it around and get the caller to pass a
pre-prepared hash containing all the bindings to askyn:

askyn("Do you wish to proceed?", "n",
:yes => lambda { puts "user pressed yes" }
:no => lambda { puts "user pressed no" }
)

That is something which immediately struck me as a possibility since I
was considering putting some configurations into a hash anyway.

askyn("Do you wish to proceed?", config = {}, &block)

I had thought that would be ugly, and I might be missing something
elegant and simple. However, perhaps that *is* the simple way to go.

I'll do a check on your other options but keep them for future use.
P.S. Note that if you nest def within a def, as your posted code does,
it probably doesn't do what you expect. The inner def will define a new
method in your object, at the time when the outer def is called.

Actually, while generating code some time back, I had accidentally
generated some methods within my run() and not noticed. The code had
worked fine. I've rectified that, but used that "mistake" here!
Thanks again.
 
G

Gregory Brown

That is something which immediately struck me as a possibility since I
was considering putting some configurations into a hash anyway.

askyn("Do you wish to proceed?", config = {}, &block)

I had thought that would be ugly, and I might be missing something
elegant and simple. However, perhaps that *is* the simple way to go.

How about this? (Someone suggested before using an object to abstract
your needs, I've only implemented it)

class Answer
def yes(&block)
@yes = block
end

def no(&block)
@no = block
end

def process(string)
(string =~ /\by(es)?\b/i ? @yes : @no).call
end
end

def ask_yn(question, &block)
ans = Answer.new
ans.instance_eval(&block)
ans.process(gets.chomp)
end

ask_yn("Are you cool?") do
yes { puts "Awesome" }
no { puts "Lame" }
end
 
N

Nit Khair

Gregory said:
How about this? (Someone suggested before using an object to abstract
your needs, I've only implemented it)

Looks awesome ! May i use it, with credits of course.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top