2009-04-13 15 views
7

È necessario convertire il seguente codice da Ruby a C#. Tuttavia, sono piuttosto sconcertato dall'uso della parola chiave yield e dalla sintassi generale di Ruby. qualcuno che conosce un po 'di Ruby si prega può dare una mano e convertire il codiceConversione da Ruby a C#

class < < Cache 
STALE_REFRESH = 1 
STALE_CREATED = 2 

# Caches data received from a block 
# 
# The difference between this method and usual Cache.get 
# is following: this method caches data and allows user 
# to re-generate data when it is expired w/o running 
# data generation code more than once so dog-pile effect 
# won't bring our servers down 
# 
def smart_get(key, ttl = nil, generation_time = 30.seconds) 
    # Fallback to default caching approach if no ttl given 
    return get(key) { yield } unless ttl 

    # Create window for data refresh 
    real_ttl = ttl + generation_time * 2 
    stale_key = "#{key}.stale" 

    # Try to get data from memcache 
    value = get(key) 
    stale = get(stale_key) 

    # If stale key has expired, it is time to re-generate our data 
    unless stale 
    put(stale_key, STALE_REFRESH, generation_time) # lock 
    value = nil # force data re-generation 
    end 

    # If no data retrieved or data re-generation forced, re-generate data and reset stale key 
    unless value 
    value = yield 
    put(key, value, real_ttl) 
    put(stale_key, STALE_CREATED, ttl) # unlock 
    end 

    return value 
end 

fine

risposta

12

Non conosco il C#, quindi qualsiasi cosa che dico di C# dovrebbe essere presa con un pizzico di sale. Tuttavia, cercherò di spiegare cosa succede in quel pezzo di codice Ruby.

class << Cache 

Ruby ha qualcosa chiamato metodi singleton. Questi non hanno nulla a che fare con il Singleton Software Design Pattern, sono solo metodi che sono definiti per uno e solo un oggetto. Pertanto, puoi avere due istanze della stessa classe e aggiungere metodi a uno di questi due oggetti.

Esistono due diverse sintassi per i metodi Singleton. Uno è solo il prefisso del nome del metodo con l'oggetto, quindi def foo.bar(baz) definirebbe un metodo bar solo per l'oggetto foo. L'altro metodo si chiama aprendo la classe singleton e sembra sintatticamente simile alla definizione di una classe, perché è anche ciò che accade semanticamente: i metodi singleton vivono in realtà in una classe invisibile che viene inserita tra l'oggetto e la sua classe effettiva nella classe gerarchia.

Questa sintassi è simile a questa: class << foo. Ciò apre la classe di oggetti singleton foo e ogni metodo definito all'interno di quel corpo di classe diventa un metodo singleton dell'oggetto foo.

Perché viene utilizzato qui? Bene, Ruby è un puro linguaggio orientato agli oggetti, il che significa che tutto, incluse le classi è un oggetto. Ora, se i metodi possono essere aggiunti a singoli oggetti e le classi sono oggetti, ciò significa che i metodi possono essere aggiunti a singole classi. In altre parole, Ruby non ha bisogno della distinzione artificiale tra metodi regolari e metodi statici (che sono comunque una frode: non sono realmente metodi, solo procedure glorificate). Qual è un metodo statico in C#, è solo un metodo normale sulla classe Singleton di un oggetto di classe.

Tutto questo è solo un modo prolisso di spiegare che tutto ciò che viene definito tra class << Cache e il corrispondente end diventa static.

STALE_REFRESH = 1 
    STALE_CREATED = 2 

In Ruby, ogni variabile che inizia con una lettera maiuscola, è in realtà una costante. Tuttavia, in questo caso non traduciamo questi come campi static const, ma piuttosto uno enum, perché è così che vengono utilizzati.

# Caches data received from a block 
    # 
    # The difference between this method and usual Cache.get 
    # is following: this method caches data and allows user 
    # to re-generate data when it is expired w/o running 
    # data generation code more than once so dog-pile effect 
    # won't bring our servers down 
    # 
    def smart_get(key, ttl = nil, generation_time = 30.seconds) 

Questo metodo ha tre parametri (quattro in realtà, vedremo esattamente il motivo per cui più tardi), due dei quali sono opzionali (ttl e generation_time). Entrambi hanno un valore predefinito, tuttavia, nel caso di ttl il valore predefinito non è realmente utilizzato, serve più come un indicatore per scoprire se l'argomento è stato passato o meno.

30.seconds è un'estensione che la libreria ActiveSupport aggiunge alla classe Integer. In realtà non fa nulla, restituisce solo self. È usato in questo caso solo per rendere più leggibile la definizione del metodo. (Esistono altri metodi che fanno qualcosa di più utile, ad esempio Integer#minutes, che restituisce self * 60 e Integer#hours e così via.) Useremo questo come indicazione, che il tipo del parametro non dovrebbe essere int ma piuttosto System.TimeSpan.

# Fallback to default caching approach if no ttl given 
    return get(key) { yield } unless ttl 

Questo contiene diversi complessi costrutti di Ruby. Cominciamo con il più semplice: i modificatori condizionali finali. Se un corpo condizionale contiene solo un'espressione, il condizionale può essere aggiunto alla fine dell'espressione. Quindi, invece di dire if a > b then foo end puoi anche dire foo if a > b. Quindi, quanto sopra è equivalente a unless ttl then return get(key) { yield } end.

Il prossimo è anche semplice: unless è solo zucchero sintattico per if not. Quindi, siamo ora allo if not ttl then return get(key) { yield } end

Terzo è il sistema di verità di Ruby. In Ruby, la verità è piuttosto semplice. In realtà, la falsità è piuttosto semplice e la verità cade naturalmente: la parola chiave speciale false è falsa e la parola chiave speciale nil è falsa, tutto il resto è vero. Quindi, in questo caso il condizionale sarà solo, se ttl è false o nil. false non è un valore sensato terribile per un intervallo di tempo, quindi l'unico interessante è nil. Lo snippet sarebbe stato scritto in modo più chiaro in questo modo: if ttl.nil? then return get(key) { yield } end. Poiché il valore predefinito per il parametro ttl è nil, questo condizionale è true, se non è stato inoltrato alcun argomento per ttl. Quindi, il condizionale è usato per capire quanti argomenti è stato chiamato il metodo, il che significa che non lo tradurremo come un condizionale ma piuttosto come un overload di metodi.

Ora, al numero yield. In Ruby, ogni metodo può accettare un blocco di codice implicito come argomento. Ecco perché ho scritto sopra che il metodo richiede in realtà quattro argomenti, non tre. Un blocco di codice è solo un pezzo di codice anonimo che può essere passato in giro, memorizzato in una variabile e invocato in seguito. Ruby eredita blocchi da Smalltalk, ma il concetto risale al 1958, alle espressioni lambda di Lisp. Alla menzione dei blocchi di codice anonimi, ma per lo meno ora, al momento di menzionare le espressioni lambda, dovresti sapere come rappresentare questo quarto parametro implicito del metodo: un tipo delegato, in particolare uno Func.

Quindi, che cosa è yield fare? Trasferisce il controllo al blocco. È fondamentalmente solo un modo molto conveniente di richiamare un blocco, senza doverlo memorizzare esplicitamente in una variabile e quindi chiamarlo.

# Create window for data refresh 
    real_ttl = ttl + generation_time * 2 
    stale_key = "#{key}.stale" 

Questa sintassi #{foo} è chiamato stringa interpolazione. Significa "sostituire il token all'interno della stringa con qualsiasi risultato di valutazione dell'espressione tra parentesi". È solo una versione molto concisa di String.Format(), che è esattamente ciò che stiamo per tradurre.

# Try to get data from memcache 
    value = get(key) 
    stale = get(stale_key) 

    # If stale key has expired, it is time to re-generate our data 
    unless stale 
     put(stale_key, STALE_REFRESH, generation_time) # lock 
     value = nil # force data re-generation 
    end 

    # If no data retrieved or data re-generation forced, re-generate data and reset stale key 
    unless value 
     value = yield 
     put(key, value, real_ttl) 
     put(stale_key, STALE_CREATED, ttl) # unlock 
    end 

    return value 
    end 
end 

Questo è il mio debole tentativo di tradurre la versione di Ruby per C#:

public class Cache<Tkey, Tvalue> { 
    enum Stale { Refresh, Created } 

    /* Caches data received from a delegate 
    * 
    * The difference between this method and usual Cache.get 
    * is following: this method caches data and allows user 
    * to re-generate data when it is expired w/o running 
    * data generation code more than once so dog-pile effect 
    * won't bring our servers down 
    */ 
    public static Tvalue SmartGet(Tkey key, TimeSpan ttl, TimeSpan generationTime, Func<Tvalue> strategy) 
    { 
     // Create window for data refresh 
     var realTtl = ttl + generationTime * 2; 
     var staleKey = String.Format("{0}stale", key); 

     // Try to get data from memcache 
     var value = Get(key); 
     var stale = Get(staleKey); 

     // If stale key has expired, it is time to re-generate our data 
     if (stale == null) 
     { 
      Put(staleKey, Stale.Refresh, generationTime); // lock 
      value = null; // force data re-generation 
     } 

     // If no data retrieved or data re-generation forced, re-generate data and reset stale key 
     if (value == null) 
     { 
      value = strategy(); 
      Put(key, value, realTtl); 
      Put(staleKey, Stale.Created, ttl) // unlock 
     } 

     return value; 
    } 

    // Fallback to default caching approach if no ttl given 
    public static Tvalue SmartGet(Tkey key, Func<Tvalue> strategy) => 
     Get(key, strategy); 

    // Simulate default argument for generationTime 
    // C# 4.0 has default arguments, so this wouldn't be needed. 
    public static Tvalue SmartGet(Tkey key, TimeSpan ttl, Func<Tvalue> strategy) => 
     SmartGet(key, ttl, new TimeSpan(0, 0, 30), strategy); 

    // Convenience overloads to allow calling it the same way as 
    // in Ruby, by just passing in the timespans as integers in 
    // seconds. 
    public static Tvalue SmartGet(Tkey key, int ttl, int generationTime, Func<Tvalue> strategy) => 
     SmartGet(key, new TimeSpan(0, 0, ttl), new TimeSpan(0, 0, generationTime), strategy); 

    public static Tvalue SmartGet(Tkey key, int ttl, Func<Tvalue> strategy) => 
     SmartGet(key, new TimeSpan(0, 0, ttl), strategy); 
} 

prega di notare che non so C#, non so .NET, non l'ho testato questo, io non so nemmeno se sia sintatticamente valido. Spero che aiuti comunque.

+0

@ Jorg - mi aiuteresti se inserisco un codice per la conversione da Ruby a C# ??? – maliks

+0

risposta perfetta: buona spiegazione + codice – trinalbadger587

5

Sembra questo codice sta passando un blocco da valutare se la cache non contiene i dati richiesti (yield è come si chiama il blocco). Questo è un codice rubino abbastanza idiomatico; Non so come (o anche se) potresti "tradurlo" in C#.

Cercare un caso d'uso per vedere cosa intendo. Si dovrebbe trovare qualcosa di vagamente simile a questo:

x = smart_get([:foo,"bar"]) { call_expensive_operation_foo("bar") } 

Una scommessa migliore sarebbe quella di capire che cosa avete bisogno di fare e scrivere qualcosa che lo fa de novo in C#, piuttosto che cercare di "tradurre" dal rubino.

+0

Si potrebbe fare qualcosa di simile con le espressioni lambda (vedi http://msdn.microsoft.com/en-us/library/bb397687.aspx) in .NET> = 3.0. –

+0

Si potrebbe, ma la traduzione di codice molto idiomatico da una lingua all'altra non funziona mai come meglio credi. Sembra sempre che si trasformi in una sorta di battute giapponesi a colpi di martello. – MarkusQ

4

Sembra che tu stia cercando di portare memcache-client da Ruby a C#. Se è così, potrebbe essere più facile da usare un'implementazione client C# memcache nativo quali:

http://code.google.com/p/beitmemcached/

ogni modo, io in genere d'accordo con MarkusQ che tradurre da un linguaggio ad alto livello ad un linguaggio di livello inferiore è probabilmente sarà meno produttivo in generale rispetto alla sola riscrittura in modo idiomatico per la lingua di destinazione. Una traduzione diretta da Ruby a C# ti darà un codice molto brutto, nel migliore dei casi.

La parola chiave rendimento Ruby consente di richiamare un blocco di codice passato come argomento implicitamente dichiarato al metodo. Così, per esempio, si può pensare di definizione del metodo smart_get come in realtà più simile:

def smart_get(key, ttl = nil, generation_time = 30.seconds, &block) 

E quando si chiama smart_get come tale:

x = smart_get("mykey", my_ttl) { do_some_operation_here } 

La roba nelle parentesi viene assegnato al blocco variabile nella definizione espansa sopra. yield fa quindi riferimento al codice nel blocco &. Questa è una semplificazione grossolana, ma dovrebbe aiutarti a ottenere l'idea generale.

Torna alla tua conversione. La semplificazione che ho appena fatto non ti porterà necessariamente al 100%, perché non appena trovi un modo C# per tradurre quel codice, un altro pezzo di codice interromperà la tua traduzione. Per esempio, diciamo che un dato metodo deve ispezionare il blocco:

def foo 
    if block.arity == 0 
     # No arguments passed, load defaults from config file and ignore call 
    else 
     yield 
    end 
end 

E, naturalmente, dal momento che lambda sono oggetti di prima classe in Ruby, è possibile riscontrare codice come il seguente:

foo = lambda { |a, b, c| a + b + c } 
# foo is now defined as a function that sums its three arguments 

E allora Dio ti aiuti se si incontra codice che definisce i metodi al volo, o (peggio fo un traduttore) prende vantaggi delle classi malleabili di Ruby:

class Foo 
    def show 
     puts "Foo" 
    end 
end 

foo = Foo.new 
foo.show # prints "Foo" 

class <&lt;foo; def show; puts "Bar"; end; end 

foo.show # prints "Bar" 

Foo.new.show # prints "Foo" 

foo.show # Still prints "Bar" 

Dato che ogni istanza o Ogni classe in Ruby può avere la sua definizione di classe, non sono sicuro di come porterei anche semplici esempi di C# senza ginnastica di fantasia.Ruby ha molte di queste funzionalità che interromperanno un semplice sforzo di traduzione, quindi consiglierei di apprendere ciò che stai cercando di eseguire, quindi di rifarlo da zero.

0

Prova questo:

def foo 
    if block.arity == 0 
     # No arguments passed, load defaults from config file and ignore call 
    else 
     yield 
    end 
end