Aggiungere codice ad un blocco, dinamicamente

Devo creare un semplice dsl, uno dei metodi è un iterazione alla quale
però vorrei utilizzare un parametro come condizione di uscita ma c’è
solo un problema, non riesco a farlo. In pratica dovrei inserire del
codice nel blocco (o proc se serve), esempio del mio approccio poco
utile ma per rendere l’idea:

class Fixnum
def tentativi options={}
self.times do |n|
tentativo = n + 1
eval(“break if #{options[:termina]}”) if options[:termina]
yield tentativo
end
end
end

irb(main):058:0> 5.tentativi(:termina => “tentativo > 2”){|t| p
"tentativo " + t.to_s}
“tentativo 1”
“tentativo 2”
=> nil

Ma dovrei fare questo:

5.tentativi(:termina => “t > 2”){|t| p "tentativo " + t.to_s}

Direi che eval non è adatto in quel contesto. Si dovrebbe poter ottenere
un comportamento simile con una lambda.
http://www.skorks.com/2010/05/ruby-procs-and-lambdas-and-the-difference-between-them/

Luigi

Non so cosa possa servire, ma con le lambda si può fare una cosa del
genere.

class Fixnum
def tentativi(options={})
self.times do |n|
tentativo = n + 1
break if options[:termina].call(tentativo) if options[:termina]
yield tentativo
end
end
end

5.tentativi({:termina => lambda {|t| t == 3 }}) {|t| p “tentativo #{t}”}

“tentativo 1”

“tentativo 2”

Luigi

Grazie per il link, l’autore più che un programmatore mi sembra un
filosofo. Comunque l’esempio non va bene, lo scopo è poter accedere allo
scope del blocco, non posso prevedere quale sia la condizione di uscita
o più genericamente il codice da eseguire.

2012/6/6 Marco M. [email protected]:

5.tentativi(:termina => “t > 2”){|t| p "tentativo " + t.to_s}

domanda: perch non passi una proc come :termina ?


twitter: @riffraff
blog (en, it): www.riffraff.info riffraff.blogsome.com
work: circleme.com

@Alessio
Così il blocco contiene una parte della logica di controllo invece deve
avere solo il contenuto.

@Gabriele
Intendi qualcosa di analogo a quello che ha proposto Luigi? Le proc
agiscono in un proprio contesto, non saprei come utilizzarle in questo
caso.

L’unico modo per avvicinarmi alla mia idea originale è con eval che mi
permette di agire nello scope di “tentativi”, per farla più semplice:

class Fixnum
def tentativi cmd=nil, &block
self.times do |n|
tentativo = n + 1
eval cmd if cmd
block.call tentativo
end
end
end
5.tentativi(“break if tentativo>2”){|t| puts “tentativo #{t}”}

ma io vorrei andare oltre ed agire nello scope del blocco. Se fosse
possibile in qualche modo poter manipolare la variabile block anzichè
poterla solo richiamare. Non so se ora è più chiaro, grazie comunque a
tutti

Senti io ti spato una cosa così al volo.

class Fixnum
def tentativi
self.times do |n|
tentativo = n + 1
continue = yield tentativo
break unless continue
end
end
end

5.tentativi { |t| t <= 2 ? (p "tentativo " + t.to_s; true) : false }

un più leggibile

5.tentativi do |t|
if t > 2
false
else
p "tentativo " + t.to_s
true
end
end

Non sono il massimo, e forse avrei usato un simbolo al posto di true
false…

tipo

class Fixnum
def tentativi
self.times do |n|
tentativo = n + 1
continue = yield tentativo
break if continue == :stop
end
end
end

5.tentativi { |t| t <= 2 ? (p "tentativo " + t.to_s) : :stop }

Scappo che sono in ritardo :wink:

nolith

Altra idea che ancora non funziona ma forse mi potete aiutare a
completarla:

module Tentativi
end

class Fixnum
def tentativi options={}, &block
block.extend Tentativi
self.times do |n|
tentativo = n + 1
if options[:termina]
Tentativi.class_eval “def break; break if #{options[:termina]}
end” if options[:termina]
block.break
end
block.call tentativo
end
block
end
end
5.tentativi(:termina => “t>2”){|t| puts “tentativo #{t}”}

NameError: undefined local variable or method `t’ for
#<Proc:[email protected](irb):33

    from (eval):1:in `break'
    from (irb):26:in `tentativi'
    from (irb):22:in `times'
    from (irb):22:in `tentativi'
    from (irb):33
    from ♥:0

2012/6/7 Marco M. [email protected]:

@Alessio
Cos il blocco contiene una parte della logica di controllo invece deve
avere solo il contenuto.

@Gabriele
Intendi qualcosa di analogo a quello che ha proposto Luigi?

si, intendevo “cosa ha l’approccio di luigi che non va bene?”

Le proc
agiscono in un proprio contesto, non saprei come utilizzarle in questo
caso.

si, ma nell’esempio che fai tu sta facendo la stessa cosa, cio il
cliente del metodo “tentativi” sta facendo il lavoro di specificare
quando il blocco deve terminare, e a quel punto tanto vale che lo
faccia con una proc invece che con una stringa.

Forse conviene che fai un esempio con quello che vuoi fare veramente
cos riusciamo ad esserti pi utili.

end
end
5.tentativi(“break if tentativo>2”){|t| puts “tentativo #{t}”}

qua lo stesso,
" eval cmd if cmd"

diventa

cmd.call if cmd

e usi throw/raise al posto di break.

se vuoi passargli lo scope visto all’interno di tentativi fai

cmd.call(binding) if cmd

ma non ha senso che chi chiama il metodo “tentativi” abbia accesso
allo scope all’interno del metodo, sarebbe l’antitesi della
programmazione strutturata.

ma io vorrei andare oltre ed agire nello scope del blocco. Se fosse
possibile in qualche modo poter manipolare la variabile block anzich
poterla solo richiamare. Non so se ora pi chiaro, grazie comunque a
tutti

puoi farlo con porcate varie tra cui: parse_tree, ripper e a occhio
con set_trace_func.
Ma una cosa terribile e io non penso che vuoi fare quello, e che
se ritorniamo al problema originale
riusciamo a fare una coda migliore.


twitter: @riffraff
blog (en, it): www.riffraff.info riffraff.blogsome.com
work: circleme.com

Ma ho scritto cosa voglio fare accedere al binding del blocco e tu mi
hai dato uno spunto incredibile:

a=“main”
def prova main_bind=nil, &block
a = “main::prova”
p eval(“a”, main_bind) if main_bind
p eval(“a”, binding)
p eval(“a”, block.call) if block_given?
end

prova binding do
a=“main::prova::block”
binding
end

dalla funzione prova ho accesso al bind del blocco!
sono ad un passo dal risultato, devo solo capire ora come accedere al
bind senza che sia il blocco a restituirlo

Tornando all’esempio originale:

class Fixnum
def tentativi options={}, &block
self.times do |n|
tentativo = n + 1
block_bind = block.call tentativo
break if options[:termina] && eval(options[:termina], block_bind)
end
end
end

time=Time.now+2
10_000.tentativi :termina => “exit_variable > time” do |t|
puts “tentativo #{t}”
#questa variabile appartiene allo scope del blocco
exit_variable = Time.now
binding
end


tentativo 4633
=> nil

Questo è un pò più chiaro:

main_a=“main”
def prova main_bind=nil, &block
local_a=“main::prova”
block_bind = block.call
p eval(“main_a”, main_bind) if main_bind
p eval(“local_a”, binding)
p eval(“block_a”, block_bind) if block_given?
end

prova binding do
block_a=“main::prova::block”
binding
end

“main”
“main::prova”
“main::prova::block”
=> nil

Quello che vorrei ottenere è un blocco pulito e poter accedere
esternamente al bind del blocco, semplicemente richiamandolo ma
block.bindind si riferisce al bind dove viene eseguito il blocco e non
all’interno del blocco:

main_a=“main”
def prova main_bind=nil, &block
local_a=“main::prova”
block.call
p eval(“main_a”, main_bind) if main_bind
p eval(“local_a”, binding)
p eval(“block_a”, block.binding) if block_given?
end

prova binding do
block_a=“main::prova::block”
end

“main”
“main::prova”
NameError: undefined local variable or method `block_a’ for main:Object
from (irb):47
from (irb):47
from :heart::0

Credo manchi poco

il motivo per cui ti chiedevo un esempio di come vuoi che sia il dsl
che poi se scrivi cos

On Thu, Jun 7, 2012 at 5:34 PM, Marco M.
[email protected] wrote:

=> nil
…significa che forse per te accettabile invocare un metodo ad hoc
nel blocco e a quel punto le cose sono molto diverse.

In particolare, una cosa tipo:

def tentativi str, &b
#definisci temporaneamente il metodo in questo oggetto
def maybe_exit!
# trova la gemma che definisce questo, o usa set_trace_func per
tracciare quando inizia il blocco e prendi il binding da l
b=binding.of_caller
eval str in b
end
#esegui il blocco qua
instance_eval(&b)
end

x.tentativi :termina => “exit_variable > time” do |t|
puts “tentativo #{t}”
exit_variable = Time.now
maybe_exit!
en

Ma son contento per te che hai capito come funzionano le closure :smiley:

(per quanto non capisco perch on scrivi

x.tentativi do |t|
puts “tentativo #{t}”
exit_variable = Time.now
break if exit_variable > time
end

)


twitter: @riffraff
blog (en, it): www.riffraff.info riffraff.blogsome.com
work: circleme.com

Grazie Gabriele, in effetti ben presto mi sono accorto di aver scoperto
l’acqua calda, troppi test tutti insieme evidentemente non mi hanno
fatto bene :slight_smile:

Inserire il codice :termina nel blocco è troppo semplice, io voglio una
vita spericolata! A parte gli scherzi, l’intento è separare il codice
del blocco da quello di controllo o per altri scopi generato in modi
diversi. Poi magari non riuscirò ad applicarlo però volevo capire bene
il funzionamento.

A proposito del tuo ultimo consiglio: non riesco a farlo funzionare!
Prendendo spunto da quà:
http://rubychallenger.blogspot.it/2011/07/caller-binding.html

ho fatto una pezza scimmiesca:

class Binding
def of_caller
cc = nil # must be present to work within lambda
count = 0 # counter of returns

set_trace_func lambda { |event, file, lineno, id, binding, klass|
  # First return gets to the caller of this method
  # (which already know its own binding).
  # Second return gets to the caller of the caller.
  # That's we want!
  if count == 2
    set_trace_func nil
    # Will return the binding to the callcc below.
    cc.call binding
  elsif event == "return"
    count += 1
  end
}
# First time it'll set the cc and return nil to the caller.
# So it's important to the caller to return again
# if it gets nil, then we get the second return.
# Second time it'll return the binding.
return callcc { |cc| }

end
end

ma viene impostato il bind sulla classe fixnum.
Non ho fatto molti tentativi anche perchè la soluzione non mi piace:

  1. devo comunque sporcare il blocco
  2. è complesso: dovrei far vedere a maybe_exit! due scope diversi: dove
    prelevare il codice di uscita e dove eseguirlo
  3. of_caller è un metodo che io giudico “spericolato” e temo un giorno
    si possa ribellare imprevedibilmente

Non perderci altro tempo, ti ringrazio tanto comunque

Quante cose che ho scoperto oggi:
se un blocco è bindato dove viene eseguito significa che condivide le
stesse variabili:

definiamo la funzione nel main scope

def prova
a=“main::prova”
yield if block_given?
a
end
=> nil

sempre nel main scope definiamo a

irb(main):006:0> a=“main”
=> “main”

eseguiamo la funzione

irb(main):007:0> prova
=> “main::prova”

irb(main):008:0> a
=> “main”

la funzione ha un bind diverso infatti crea una copia di a che non
interferisce col main scope

ora eseguiamo un blocco

prova do
a=“main::prova::block”
b=“main::prova::block”
end
=> “main::prova”

controlliamo a del main scope

irb(main):013:0> a
=> “main::prova::block”

il blocco utilizza lo stesso bind del main ed infatti a è stata
modificata, b invece non esisteva ed è stata eliminata

irb(main):014:0> b
NameError: undefined local variable or method `b’ for main:Object
from (irb):14