2009-04-13 24 views
7

Necesita convertir el siguiente código de Ruby a C#. Sin embargo, estoy desconcertado por el uso de la palabra clave yield y la sintaxis general de Ruby. ¿Puede alguien que sabe un poco de Ruby colabora y convertir el códigoConvirtiendo Ruby en 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 

final

Respuesta

12

No sé C# en absoluto, así que cualquier cosa que diga sobre C# debe tomarse con un grano de sal. Sin embargo, intentaré explicar qué sucede en esa pieza de código de Ruby.

class << Cache 

Ruby tiene algo que se llama métodos simples. No tienen nada que ver con el patrón de diseño de software de Singleton, solo son métodos que están definidos para un solo objeto. Entonces, puede tener dos instancias de la misma clase y agregar métodos a uno de esos dos objetos.

Existen dos sintaxis diferentes para los métodos singleton. Una es simplemente prefijar el nombre del método con el objeto, por lo que def foo.bar(baz) definiría un método bar solo para el objeto foo. El otro método se llama abriendo la clase singleton y se parece sintácticamente similar a definir una clase, porque eso es también lo que ocurre semánticamente: los métodos singleton en realidad viven en una clase invisible que se inserta entre el objeto y su clase real en la clase jerarquía.

Esta sintaxis se ve así: class << foo. Esto abre la clase de singleton del objeto foo y cada método definido dentro de ese cuerpo de clase se convierte en un método singleton del objeto foo.

¿Por qué se usa aquí? Bueno, Ruby es un lenguaje puro orientado a objetos, lo que significa que todo, incluyendo clases es un objeto. Ahora, si los métodos se pueden agregar a objetos individuales, y las clases son objetos, esto significa que los métodos se pueden agregar a clases individuales. En otras palabras, Ruby no tiene necesidad de la distinción artificial entre métodos regulares y métodos estáticos (que son un fraude, de todos modos: no son realmente métodos, solo procedimientos glorificados). Lo que es un método estático en C#, es solo un método regular en la clase de singleton de un objeto de clase.

Todo esto es solo una forma larga de explicar que todo lo definido entre class << Cache y su correspondiente end se convierte en static.

STALE_REFRESH = 1 
    STALE_CREATED = 2 

En Ruby, cada variable que comienza con una letra mayúscula, es en realidad una constante. Sin embargo, en este caso no los traduciremos como campos static const, sino más bien como enum, porque así es como se usan.

# 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) 

Este método tiene tres parámetros (cuatro en realidad, vamos a ver exactamente qué más adelante), dos de ellos son opcionales (ttl y generation_time). Ambos tienen un valor predeterminado, sin embargo, en el caso de ttl el valor predeterminado no se usa realmente, sirve más como un marcador para saber si el argumento se pasó o no.

30.seconds es una extensión que la biblioteca ActiveSupport agrega a la clase Integer. En realidad, no hace nada, simplemente devuelve self. Se usa en este caso solo para hacer que la definición del método sea más legible. (Hay otros métodos que hacen algo más útil, por ejemplo, Integer#minutes, que devuelve self * 60 y Integer#hours y así sucesivamente.) Utilizaremos esto como una indicación, que el tipo de parámetro no debe ser int, sino más bien System.TimeSpan.

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

Esto contiene varias construcciones complejas de Ruby. Comencemos con el más fácil: modificadores condicionales finales. Si un cuerpo condicional contiene solo una expresión, entonces el condicional se puede agregar al final de la expresión. Entonces, en lugar de decir if a > b then foo end, también puede decir foo if a > b. Entonces, lo anterior es equivalente a unless ttl then return get(key) { yield } end.

El siguiente también es fácil: unless es solo azúcar sintáctica para if not. Entonces, ahora estamos en if not ttl then return get(key) { yield } end

El tercer es el sistema de verdad de Ruby. En Ruby, la verdad es bastante simple. En realidad, la falsedad es bastante simple, y la verdad cae naturalmente: la palabra clave especial false es falsa, y la palabra clave especial nil es falsa, todo lo demás es verdadero. Por lo tanto, en este caso, el condicional será solo verdadero, si ttl es o nil. false no es un valor razonablemente terrible para un período de tiempo, por lo que el único interesante es nil. El fragmento se habría escrito más claramente así: if ttl.nil? then return get(key) { yield } end. Como el valor predeterminado para el parámetro ttl es nil, este condicional es verdadero, si no se pasó ningún argumento para ttl. Por lo tanto, el condicional se usa para determinar con cuántos argumentos se llamó el método, lo que significa que no vamos a traducirlo como una condicional sino como una sobrecarga de método.

Ahora, en el yield. En Ruby, cada método puede aceptar un bloque de código implícito como argumento. Es por eso que escribí anteriormente que el método realmente toma cuatro argumentos, no tres. Un bloque de código es simplemente una pieza anónima de código que puede pasarse, almacenarse en una variable e invocarse más tarde. Ruby hereda bloques de Smalltalk, pero el concepto se remonta a 1958, a las expresiones lambda de Lisp. Al mencionar bloques de código anónimos, pero al menos ahora, al mencionar expresiones lambda, debe saber cómo representar este cuarto parámetro de método implícito: un tipo de delegado, más específicamente, un Func.

Entonces, ¿qué es yield do? Transfiere el control al bloque. Básicamente es solo una forma muy conveniente de invocar un bloque, sin tener que almacenarlo explícitamente en una variable y luego llamarlo.

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

Esta sintaxis #{foo} se llama cadena de interpolación. Significa "reemplazar el token dentro de la cadena con el resultado de evaluar la expresión entre llaves". Es solo una versión muy concisa de String.Format(), que es exactamente a lo que vamos a traducirlo.

# 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 

Ésta es mi débil intento de traducción de la versión de Ruby a 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); 
} 

Tenga en cuenta que no sé C#, no sé .NET, no he probado esto, Ni siquiera sé si es sintácticamente válido. Espero que ayude de todos modos.

+0

@ Jorg - ¿me ayudarás si publico un código para la conversión de Ruby a C#? – maliks

+0

respuesta perfecta: buena explicación + código – trinalbadger587

5

Parece que este código se pasa un bloque para ser evaluado si la caché no contiene los datos solicitados (yield es como llamas al bloque). Este es un código de rubís bastante idiomático; No sé cómo (o incluso si) podrías "traducirlo" a C#.

Busque un caso de uso para ver a qué me refiero. Usted debe encontrar algo vagamente como esto:

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

una mejor apuesta sería la de averiguar lo que necesita hacer y escribir algo que hace que de novo en C#, en lugar de tratar de "traducir" de rubí.

+0

Se podría hacer algo similar con las expresiones lambda (ver http://msdn.microsoft.com/en-us/library/bb397687.aspx) en .NET> = 3.0. –

+0

Podrías, pero traducir un código altamente idiomático de un idioma a otro nunca funciona tan bien como te gustaría. Siempre parece ser una especie de bromas japonesas. – MarkusQ

4

Parece que estás intentando portar memcache-client de Ruby a C#. Si es así, puede ser que sea más fácil de usar una aplicación cliente de C# Memcache nativa, tales como:

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

De cualquier manera, por lo general de acuerdo con MarkusQ que la traducción de un lenguaje de alto nivel a un lenguaje de bajo nivel es probablemente va a ser menos productivo en general que solo reescribir de manera idiomática para el idioma de destino. Una traducción directa de Ruby a C# te dará un código muy feo, en el mejor de los casos.

La clave de rendimiento de Ruby le permite invocar un bloque de código que se pasó como un argumento implícitamente declarado al método. Así, por ejemplo, se puede pensar en la definición del método smart_get como realidad más parecido a:

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

Y cuando se llama smart_get como tal:

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

Las cosas en las llaves se le asigna a la bloque variable en la definición expandida anterior. yield invoca el código en el bloque &. Esta es una gran simplificación, pero debería ayudarlo a tener una idea general.

Volver a la conversión. La simplificación que acabo de hacer no necesariamente lo hará llegar al 100% allí, porque tan pronto como encuentre una forma C# para traducir ese código, otro fragmento de código romperá su traducción. Por ejemplo, digamos que un determinado método necesita para inspeccionar el bloque:

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

Y, por supuesto, ya lambdas son objetos de primera clase en Ruby, puede encontrarse con un código como el siguiente:

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

Y entonces Dios le ayude si se encuentra con el código que define los métodos sobre la marcha, o (peor fo un traductor) toma ventajas de las clases maleables de 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" 

Dado que cada instancia o Si cada clase en Ruby puede tener su propia definición de clase, no estoy muy seguro de cómo convertiría incluso ejemplos simples en C# sin gimnasia de lujo.Ruby tiene muchas de estas características que romperán un simple esfuerzo de traducción, por lo que recomendaría aprender lo que estás intentando exportar y luego volver a hacerlo desde cero.

0

Prueba esto:

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