2010-02-22 17 views
5

Desidero una funzione che mantenga lo stato locale in Ruby. Ogni volta che chiamo la funzione voglio restituire un risultato che dipende sia da un argomento di chiamata sia dallo stato memorizzato della funzione. Ecco un semplice esempio:Ruby: funzioni di ripristino con argomenti

def inc_mult(factor) 
    @state ||= 0 # initialize the state the first time. 
    @state += 1 # adjust the internal state. 
    factor * @state 
end 

Si noti che lo stato viene inizializzato la prima volta, ma le chiamate successive accedono allo stato memorizzato. Questo è buono, tranne che le perdite @state nel contesto circostante, che non voglio.

Qual è il modo più elegante di riscrivere questo in modo che @state non presenti perdite?

(Nota: Il mio esempio reale è molto più complicato, e l'inizializzazione lo stato è costoso.)

+0

Io non sono troppo familiarità con loro, ma isn' t questo quali sono le continuazioni? Guarderei in 'callcc' per maggiori informazioni. –

risposta

4

Probabilmente si desidera incapsulare inc_mult nella propria classe, poiché si desidera incapsulare il suo stato separatamente dal relativo oggetto contenitore. Ecco come i generatori (la dichiarazione yield) funzionano in Python e C#.

Qualcosa di semplice come questo lo farebbe:

class Foo 
    state = 0 
    define_method(:[]) do |factor| 
    state += 1 
    factor * state 
    end 
end 

Filosoficamente, penso che quello che stai puntando è incompatibile con la vista di Ruby di metodi come messaggi, piuttosto che come funzioni che possono in qualche modo stare in piedi da solo.

+0

sì, penso che tu (e @ Mike Trpcic) abbia ragione che quello che voglio fare non sia filosoficamente giusto ... molto interessante da pensare! – Peter

0

Sembra che si potrebbe utilizzare un globale o una variabile di classe in qualche altra classe, che avrebbe almeno permettervi di saltare il contesto immediatamente circostante.

+0

sicuro, ma questo non è terribilmente elegante ... sembra che 'inc_mult' dovrebbe essere in grado di badare al proprio stato. – Peter

2

Le funzioni sono senza stato. Sono codice procedurale. Le classi contengono lo stato e il codice procedurale. Il modo più elegante per fare questo sarebbe quello di seguire il corretto paradigma di programmazione:

Classe di mantenere lo stato
Funzione di manipolare stato

Dal momento che si sta utilizzando Ruby, può sembrare un po ' più elegante per te per mettere queste cose in un modulo che può essere incluso. Il modulo può gestire il mantenimento dello stato, e il metodo potrebbe semplicemente essere chiamato tramite:

require 'incmodule' 
IncModule::inc_mult(10) 

O qualcosa di simile

+2

Per essere pignoli, ** gli oggetti ** contengono lo stato e hanno ** metodi ** per manipolare lo stato. –

+3

@John Topley: Quando leggo o ascolto qualcosa del genere, immagino sempre il sergente. Hartman di Full Metal Jacket: "Si chiama * oggetto * - orientato non * di classe * - orientato, dannazione! Goccia e dammi 50, larva!" Fidati di me, aiuta oltre la frustrazione. –

0

Beh, si potrebbe giocare un po '... Che dire di una funzione che si riscrive?

def imult(factor) 
    state = 1; 
    rewrite_with_state(state+1) 
    factor*state 
end 

def rewrite_with_state(state) 
    eval "def imult(factor); state = #{state}; rewrite_with_state(#{state+1}); factor*state; end;" 
end 

Attenzione: questo è estremamente brutto e non deve essere utilizzato nel codice di produzione!

1

Desidero una funzione che mantiene lo stato locale in Ruby.

Quella parola "funzione" dovrebbe immediatamente sollevare un grosso segnale rosso lampeggiante che sta utilizzando un linguaggio di programmazione errato. Se vuoi funzioni, dovresti usare un linguaggio di programmazione funzionale, non un linguaggio orientato agli oggetti.In un linguaggio di programmazione funzionale, le funzioni di solito vicino sul loro ambiente lessicale, che ti fa ciò che si sta cercando di fare assolutamente banale:

var state; 
function incMult(factor) { 
    if (state === undefined) { 
     state = 0; 
    } 
    state += 1; 
    return factor * state; 
} 
print(incMult(2)); // => 2 
print(incMult(2)); // => 4 
print(incMult(2)); // => 6 

Questo particolare esempio è in ECMAScript, ma sembra più o meno lo stesso in qualsiasi linguaggio di programmazione funzionale.

[Nota: Mi rendo conto che non è un buon esempio, perché ECMAScript è in realtà anche un linguaggio orientato agli oggetti, sia perché ha rotto la semantica di ambito atually dire che state perdite in questo caso, anche. In una lingua con una semantica di ambito appropriata (e in un paio di anni, ECMAScript sarà uno di questi), funzionerà come previsto. Ho usato ECMAScript principalmente per la sua sintassi familiare, non come esempio di un buon linguaggio funzionale.]

Questo è il modo in cui lo stato è incapsulato in linguaggi funzionali poiché, beh, poiché ci sono linguaggi funzionali, tutto il ritorno a calcolo lambda.

Tuttavia, negli anni '60 alcune persone intelligenti hanno notato che si trattava di uno schema molto comune, e hanno deciso che questo modello era così comune a che meritava la propria lingua. E così, l'oggetto è nato.

Quindi, in un linguaggio orientato agli oggetti, anziché utilizzare le chiusure funzionali per incapsulare lo stato, usereste gli oggetti. Come avrai notato, i metodi in Ruby non si chiudono nel loro ambiente lessicale, a differenza delle funzioni nei linguaggi di programmazione funzionale. E questo è precisamente il motivo: perché l'incapsulamento dello stato è raggiunto attraverso altri mezzi.

Così, in Ruby si usa un oggetto come questo:

inc_mult = Object.new 
def inc_mult.call(factor) 
    @state ||= 0 
    @state += 1 
    factor * @state 
end 
p inc_mult.(2) # => 2 
p inc_mult.(2) # => 4 
p inc_mult.(2) # => 6 

[Sidenote: Questa corrispondenza 1: 1 è quello che i programmatori funzionali stanno parlando quando dicono "gli oggetti sono solo le chiusure di un uomo povero" . Naturalmente, i programmatori orientati agli oggetti di solito contrastano con "le chiusure sono solo oggetti di un povero uomo". E la cosa divertente è che entrambi hanno ragione e nessuno di loro lo comprende.]

Ora, per completezza, voglio sottolineare che mentre i metodi non si chiudono nel loro ambiente lessicale, c'è un costrutto in Ruby, che fa: blocchi. (È interessante notare che i blocchi non sono oggetti.) E, dato che è possibile definire i metodi utilizzando i blocchi, è anche possibile definire i metodi che sono le chiusure:

foo = Object.new 
state = nil 
foo.define_singleton_method :inc_mult do |factor| 
    state ||= 0 
    state += 1 
    factor * state 
end 
p foo.inc_mult(2) # => 2 
p foo.inc_mult(2) # => 4 
p foo.inc_mult(2) # => 6 
0

è possibile utilizzare lambda. ad esempio,

$ cat test.rb

def mk_lambda(init = 0) 
    state = init 
    ->(factor=1, incr=nil){ 
    state += incr || 1; 
    puts "state now is: #{state}" 
    factor * state 
    } 
end 

f = mk_lambda 

p f[] 
p f[1] 
p f[2] 
p f[100] 
p f[100,50] 
p f[100] 

$ rubino test.rb

state now is: 1 
1 
state now is: 2 
2 
state now is: 3 
6 
state now is: 4 
400 
state now is: 54 
5400 
state now is: 55 
5500 

Cordiali saluti -botp

Problemi correlati