Forum: Italian Ruby user group Aggiungere codice ad un blocco, dinamicamente

Posted by Marco Mastrodonato (marcomd)
on 2012-06-06 12:08
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}
Posted by Luigi Maselli - grigio.org (grigio)
on 2012-06-06 12:41
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-lambd...

Luigi
Posted by Luigi Maselli - grigio.org (grigio)
on 2012-06-06 14:41
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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-06 17:18
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.
Posted by Alessio Caiazza (nolith)
on 2012-06-06 17:59
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 ;)


nolith
Posted by gabriele renzi (Guest)
on 2012-06-06 18:46
(Received via mailing list)
2012/6/6 Marco Mastrodonato <m.mastrodonato@gmail.com>:


> 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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 09:18
@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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 10:07
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:0x047cba58@(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
Posted by gabriele renzi (Guest)
on 2012-06-07 12:55
(Received via mailing list)
2012/6/7 Marco Mastrodonato <m.mastrodonato@gmail.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?

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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 15:29
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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 16:59
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 ♥:0


Credo manchi poco
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 17:34
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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-07 18:01
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
Posted by gabriele renzi (Guest)
on 2012-06-07 23:03
(Received via mailing list)
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 Mastrodonato
<m.mastrodonato@gmail.com> 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 :D

(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
Posted by Marco Mastrodonato (marcomd)
on 2012-06-12 17:07
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 :)

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-b...

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
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.