2009-04-13 6 views
7

Besoin de convertir le code suivant de Ruby en C#. Cependant, je suis un peu déconcerté par l'utilisation du mot-clé yield et de la syntaxe générale de Ruby. Quelqu'un peut-il qui connaît un peu Ruby s'il vous plaît aider et convertir le codeConversion de 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 

fin

Répondre

12

Je ne connais pas du tout C#, donc tout ce que je dis à propos de C# devrait être pris avec un grain de sel. Cependant, je vais essayer d'expliquer ce qui se passe dans ce morceau de code Ruby.

class << Cache 

Ruby a quelque chose appelé méthodes singleton. Cela n'a rien à voir avec le Singleton Software Design Pattern, ce sont juste des méthodes qui sont définies pour un et un seul objet. Vous pouvez donc avoir deux instances de la même classe et ajouter des méthodes à l'un de ces deux objets.

Il existe deux syntaxes différentes pour les méthodes singleton. L'une consiste simplement à préfixer le nom de la méthode avec l'objet, donc def foo.bar(baz) définirait une méthode bar uniquement pour l'objet foo. L'autre méthode est appelée ouvrant la classe singleton et ressemble syntaxiquement à la définition d'une classe, parce que c'est aussi ce qui se passe sémantiquement: les méthodes singleton vivent réellement dans une classe invisible qui est insérée entre l'objet et sa classe dans la classe hiérarchie.

Cette syntaxe ressemble à ceci: class << foo. Cela ouvre la classe singleton de l'objet foo et chaque méthode définie à l'intérieur de ce corps de classe devient une méthode singleton de l'objet foo.

Pourquoi est-ce utilisé ici? Eh bien, Ruby est un langage orienté objet pur, ce qui signifie que tout, y compris les classes est un objet. Maintenant, si des méthodes peuvent être ajoutées à des objets individuels et si les classes sont des objets, cela signifie que des méthodes peuvent être ajoutées à des classes individuelles. En d'autres termes, Ruby n'a pas besoin de la distinction artificielle entre les méthodes régulières et les méthodes statiques (qui sont une fraude, de toute façon: ce ne sont pas vraiment des méthodes, seulement des procédures glorifiées). Qu'est-ce qu'une méthode statique en C#, est juste une méthode régulière sur la classe singleton d'un objet de classe. Tout cela est juste une façon de dire que tout ce qui est défini entre class << Cache et end devient static.

STALE_REFRESH = 1 
    STALE_CREATED = 2 

En Ruby, toute variable commençant par une majuscule est en fait une constante. Cependant, dans ce cas, nous ne traduirons pas ces champs en static const, mais plutôt en enum, parce que c'est ainsi qu'ils sont utilisés.

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

Cette méthode a trois paramètres (quatre en fait, nous verrons exactement pourquoi plus tard), deux d'entre eux sont facultatifs (ttl et generation_time). Les deux ont une valeur par défaut, cependant, dans le cas de ttl la valeur par défaut n'est pas vraiment utilisée, elle sert davantage de marqueur pour savoir si l'argument a été passé ou non.

30.seconds est une extension que la bibliothèque ActiveSupport ajoute à la classe Integer. Il ne fait rien, il renvoie juste self. Il est utilisé dans ce cas juste pour rendre la définition de la méthode plus lisible. (Il existe d'autres méthodes qui font quelque chose de plus utile, par exemple Integer#minutes, qui renvoie self * 60 et Integer#hours et ainsi de suite.Nous utiliserons ceci comme une indication, que le type du paramètre ne doit pas être int mais plutôt System.TimeSpan.

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

Ceci contient plusieurs constructions complexes de Ruby. Commençons par le plus simple: les modificateurs conditionnels à la fin. Si un corps conditionnel contient une seule expression, le conditionnel peut être ajouté à la fin de l'expression. Donc, au lieu de dire if a > b then foo end, vous pouvez également dire foo if a > b. Donc, ce qui précède est équivalent à unless ttl then return get(key) { yield } end.

Le prochain est également facile: unless est juste du sucre syntaxique pour if not. Donc, nous sommes maintenant à if not ttl then return get(key) { yield } end

Troisième est le système de vérité de Ruby. En Ruby, la vérité est assez simple. En fait, la fausseté est assez simple, et la vérité tombe naturellement: le mot-clé spécial false est faux, et le mot-clé spécial nil est faux, tout le reste est vrai. Donc, dans ce cas, le conditionnel seulement sera vrai, si ttl est false ou nil. false n'est pas une valeur sensible terrible pour un laps de temps, de sorte que le seul intéressant est nil. L'extrait aurait été plus clairement écrit comme ceci: if ttl.nil? then return get(key) { yield } end. Étant donné que la valeur par défaut du paramètre ttl est nil, cette condition est vraie si aucun argument n'a été passé pour ttl. Ainsi, le conditionnel est utilisé pour déterminer avec combien d'arguments la méthode a été appelée, ce qui signifie que nous n'allons pas le traduire comme conditionnel mais plutôt comme une surcharge de méthode.

Maintenant, sur le yield. Dans Ruby, chaque méthode peut accepter un bloc de code implicite comme argument. C'est pourquoi j'ai écrit ci-dessus que la méthode prend effectivement quatre arguments, pas trois. Un bloc de code est simplement un morceau de code anonyme qui peut être transmis, stocké dans une variable et invoqué plus tard. Ruby hérite des blocs de Smalltalk, mais le concept remonte à 1958, aux expressions lambda de Lisp. A la mention des blocs de code anonymes, mais à tout le moins maintenant, à la mention des expressions lambda, vous devez savoir représenter ce quatrième paramètre de méthode implicite: un type de délégué, plus précisément un Func.

Alors, qu'est-ce que yield? Il transfère le contrôle au bloc. C'est fondamentalement un moyen très pratique d'invoquer un bloc, sans devoir le stocker explicitement dans une variable et ensuite l'appeler.

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

Cette syntaxe #{foo} est appelée interpolation de chaîne. Cela signifie "remplacer le jeton à l'intérieur de la chaîne avec le résultat de l'évaluation de l'expression entre les accolades". C'est juste une version très concise de String.Format(), qui est exactement ce que nous allons traduire.

# 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 

Ceci est ma faible tentative de traduire la version Ruby à 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); 
} 

S'il vous plaît noter que je ne sais pas C#, je ne sais pas .NET, je n'ai pas testé, je Je ne sais même pas si c'est syntaxiquement valide. J'espère que ça aide quand même.

+0

@ Jorg - pouvez-vous m'aider si je poste un code pour Ruby à la conversion C# ??? – maliks

+0

réponse parfaite: bonne explication + code – trinalbadger587

5

Il semble que ce code est passé un bloc à évaluer si le cache ne contient pas les données demandées (yield est comment vous appelez le bloc). C'est un code rubis assez idiomatique; Je ne sais pas comment (ou même si) vous pourriez le "traduire" en C#.

Rechercher un cas d'utilisation pour voir ce que je veux dire. Vous devriez trouver quelque chose de vaguement comme ceci:

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

Un meilleur pari serait de savoir ce que vous avez besoin de faire et d'écrire quelque chose qui fait que de novo en C#, plutôt que d'essayer de « traduire » du rubis.

+0

On pourrait faire quelque chose de similaire avec des expressions lambda (voir http://msdn.microsoft.com/en-us/library/bb397687.aspx) dans .NET> = 3.0. –

+0

Vous pourriez, mais traduire le code fortement idiomatique d'une langue à l'autre ne fonctionne jamais aussi bien que vous le souhaitez. Il semble toujours se produire comme des blagues japonaises. – MarkusQ

4

Il semble que vous essayez de porter le client memcache de Ruby vers C#. Dans ce cas, il pourrait être plus facile d'utiliser une implémentation client natif C# memcache tels que:

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

Quoi qu'il en soit, je suis généralement d'accord avec MarkusQ que la traduction d'une langue de niveau supérieur à une langue de niveau inférieur est probablement moins productif que de simplement réécrire de façon idiomatique pour la langue cible. Une traduction directe de Ruby à C# va vous donner un code très moche, au mieux. Le mot-clé yield Ruby vous permet d'invoquer un bloc de code qui a été transmis en tant qu'argument implicitement déclaré à la méthode. Ainsi, par exemple, vous pouvez penser à la définition de la méthode smart_get que la recherche en fait plus:

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

Et lorsque vous appelez smart_get en tant que tel:

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

Les choses dans les accolades est attribué, à la bloc variable dans la définition étendue ci-dessus. yield appelle ensuite le code dans le bloc &. Ceci est une simplification grossière, mais devrait vous aider à obtenir l'idée générale.

Retour à votre conversion. La simplification que je viens de faire ne va pas forcément vous arriver à 100%, car dès que vous trouvez un moyen de traduire ce code en C#, un autre morceau de code va casser votre traduction. Par exemple, disons une méthode donnée doit inspecter le bloc:

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

Et bien sûr, puisque lambdas sont des objets de première classe en Ruby, vous pouvez rencontrer code comme suit:

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

Et alors Dieu vous aider si vous rencontrez code qui définit des méthodes à la volée, ou (pire fo traducteur) prend des avantages des classes malléables 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" 

Depuis chaque instance o Si chaque classe de Ruby peut avoir sa propre définition de classe, je ne suis pas vraiment sûr de savoir comment vous pourriez même transférer des exemples simples à C# sans gymnastique sophistiquée.Ruby a beaucoup de ces fonctionnalités qui vont casser un effort de traduction simple, donc je vous recommande d'apprendre la chose que vous essayez de porter, puis de le refaire à partir de rien.

0

Essayez ceci:

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