Преобразование Ruby в C # - PullRequest
       12

Преобразование Ruby в C #

7 голосов
/ 13 апреля 2009

Необходимо преобразовать следующий код из Ruby в C #. Однако я несколько озадачен использованием ключевого слова yield и общего синтаксиса Ruby. Может кто-нибудь, кто знает немного Ruby, пожалуйста, помогите и конвертировать код

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

конец

Ответы [ 4 ]

12 голосов
/ 13 апреля 2009

Я совсем не знаю C #, поэтому все, что я говорю о C #, следует воспринимать с небольшим количеством соли. Однако я попытаюсь объяснить, что происходит в этом фрагменте кода Ruby.

class << Cache

В Ruby есть нечто, называемое одноэлементными методами . Они не имеют ничего общего с шаблоном проектирования программного обеспечения Singleton, это просто методы, которые определены для одного и только одного объекта. Таким образом, вы можете иметь два экземпляра одного класса и добавлять методы к одному из этих двух объектов.

Существует два разных синтаксиса для одноэлементных методов. Одним из них является просто добавление имени метода к объекту, поэтому def foo.bar(baz) будет определять метод bar только для объекта foo. Другой метод называется , открывающий одноэлементный класс , и он синтаксически похож на определение класса, потому что это также то, что происходит семантически: на самом деле одноэлементные методы живут в невидимом классе, который вставляется между объектом и его фактическим класс в иерархии классов.

Этот синтаксис выглядит следующим образом: class << foo. Это открывает одноэлементный класс объекта foo, и каждый метод, определенный внутри этого тела класса, становится одноэлементным методом объекта foo.

Почему это используется здесь? Ну, Ruby - это чисто объектно-ориентированный язык, что означает, что все , включая классы, является объектом. Теперь, если методы могут быть добавлены к отдельным объектам, а классы являются объектами, это означает, что методы могут быть добавлены к отдельным классам. Другими словами, в Ruby нет необходимости в искусственном различении между обычными и статическими методами (которые, во всяком случае, являются мошенничеством: на самом деле это не методы, а прославленные процедуры). Что такое статический метод в C #, это обычный метод для одноэлементного класса объекта класса.

Все это просто изнурительный способ объяснить, что все, что определено между class << Cache и соответствующими end, становится static.

  STALE_REFRESH = 1
  STALE_CREATED = 2

В Ruby каждая переменная, которая начинается с заглавной буквы, на самом деле является константой. Однако в этом случае мы не будем переводить их как static const поля, а скорее enum, потому что именно так они используются.

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

Этот метод имеет три параметра (на самом деле четыре, мы точно увидим , почему позже), два из них являются необязательными (ttl и generation_time). У обоих из них есть значение по умолчанию, однако, в случае ttl значение по умолчанию на самом деле не используется, оно служит скорее маркером для определения, был ли передан аргумент или нет.

30.seconds - это расширение, которое библиотека ActiveSupport добавляет к классу Integer. На самом деле он ничего не делает, он просто возвращает self. В этом случае он используется только для того, чтобы сделать определение метода более читабельным. (Существуют другие методы, которые делают что-то более полезное, например, Integer#minutes, который возвращает self * 60 и Integer#hours и т. Д.) Мы будем использовать это как указание, что тип параметра не должен быть int а точнее System.TimeSpan.

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

Содержит несколько сложных конструкций Ruby. Начнем с самого простого: конечные условные модификаторы. Если условное тело содержит только одно выражение, то условное выражение может быть добавлено в конец выражения. Таким образом, вместо того, чтобы сказать if a > b then foo end, вы также можете сказать foo if a > b. Итак, вышесказанное эквивалентно unless ttl then return get(key) { yield } end.

Следующее также легко: unless - просто синтаксический сахар для if not. Итак, мы сейчас находимся на if not ttl then return get(key) { yield } end

Третье - система правды Руби. В Ruby правда довольно проста. На самом деле ложность довольно проста, и истина естественна: специальное ключевое слово false ложно, а специальное ключевое слово nil ложно, все остальное верно. Таким образом, в этом случае условное будет только истинным, если ttl равно false или nil. false не является ужасным разумным значением для временного интервала, поэтому единственное интересное - это nil. Фрагмент был бы написан более четко так: if ttl.nil? then return get(key) { yield } end. Поскольку значение по умолчанию для параметра ttl равно nil, это условие имеет значение true, если для ttl не было передано ни одного аргумента. Таким образом, условное выражение используется для определения количества аргументов, вызванных методом, что означает, что мы не будем переводить его как условное выражение, а скорее как перегрузку метода.

Теперь перейдем к yield. В Ruby каждый метод может принимать неявный блок кода в качестве аргумента. Вот почему я писал выше, что метод на самом деле принимает четыре аргумента, а не три. Блок кода - это просто анонимный фрагмент кода, который можно передать, сохранить в переменной и вызвать позже. Ruby наследует блоки от Smalltalk, но концепция восходит к 1958 году, к лямбда-выражениям Лиспа. При упоминании анонимных блоков кода, но по крайней мере сейчас, при упоминании лямбда-выражений, вы должны знать, как представить этот неявный четвертый параметр метода: тип делегата, точнее, Func.

Итак, что делает yield? Он передает управление блоку. Это в основном просто очень удобный способ вызова блока, без необходимости явно сохранять его в переменной и затем вызывать его.

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

Этот синтаксис #{foo} называется интерполяция строк . Это означает «заменить токен внутри строки любым результатом вычисления выражения между фигурными скобками». Это просто очень краткая версия String.Format(), и именно это мы и собираемся перевести.

    # 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

Это моя слабая попытка перевести версию 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);
}

Обратите внимание, что я не знаю C #, я не знаю .NET, я не проверял это, я даже не знаю, является ли он синтаксически допустимым. Надеюсь, это поможет в любом случае.

5 голосов
/ 13 апреля 2009

Похоже, что этот код передается блоку для оценки, если кэш не содержит запрошенных данных (yield - это способ вызова блока). Это довольно идиоматичный код ruby; Я не знаю, как (или даже если) вы могли бы "перевести это" на c #.

Ищите вариант использования, чтобы понять, что я имею в виду. Вы должны найти что-то неопределенно похожее на это:

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

Лучше было бы выяснить, что вам нужно сделать, и написать что-то, что делает это de novo в c #, а не пытаться "переводить" с ruby.

4 голосов
/ 13 апреля 2009

Похоже, вы пытаетесь портировать memcache-client с Ruby на C #. Если это так, может быть проще использовать собственную реализацию клиента C # memcache, например:

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

В любом случае, я в целом согласен с MarkusQ, что перевод с языка более высокого уровня на язык более низкого уровня, вероятно, будет в целом менее продуктивным, чем просто переписывание идиоматическим способом для целевого языка. Прямой перевод с Ruby на C # в лучшем случае даст вам очень уродливый код.

Ключевое слово yield Ruby позволяет вам вызывать блок кода, который был передан в качестве неявно объявленного аргумента методу. Так, например, вы можете считать определение метода smart_get действительно похожим на:

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

А когда вы вызываете smart_get как таковой:

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

Материал в фигурных скобках присваивается блоку переменных в расширенном определении выше. yield затем вызывает код в & block. Это грубое упрощение, но оно должно помочь вам получить общее представление.

Вернуться к вашей конверсии. Упрощение, которое я только что сделал, не обязательно приведет вас на 100%, потому что, как только вы найдете способ перевода этого кода на C #, другой фрагмент кода нарушит ваш перевод. Например, допустим, что данный метод должен проверить блок:

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

И, конечно же, поскольку лямбды - это первоклассные объекты в Ruby, вы можете столкнуться с кодом, подобным следующему:

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

И тогда Бог поможет вам, если вы столкнетесь с кодом, который определяет методы на лету, или (хуже переводчика) воспользуется гибкими классами Руби:

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"

Поскольку каждый экземпляр каждого класса в Ruby может иметь свое собственное определение класса, я не совсем уверен, как вы могли бы портировать даже простые примеры на C # без причудливой гимнастики. В Ruby есть множество таких функций, которые не позволят выполнить простую работу по переводу, поэтому я бы порекомендовал изучить то, что вы пытаетесь перенести, а затем повторить его с нуля.

0 голосов
/ 23 февраля 2014

Попробуйте это:

def foo
   if block.arity == 0
      # No arguments passed, load defaults from config file and ignore call
   else
      yield
   end
end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...