Каковы преимущества Отложенных вычислений?

Что преимущества там к Отложенным вычислениям в противоположность Нетерпеливой Оценке?

Что производительность наверху там? Отложенные вычисления собираются быть медленнее или быстрее? Почему (или это зависит от реализации?)?

Как отложенные вычисления на самом деле работают в большинстве реализаций? Мне казалось бы, что это будет намного медленнее и интенсивно использующим память, потому что переменные должны сохраненные операции, а также числа. таким образом, как это работает на языке как Haskell (примечание, я на самом деле не знаю тот язык)? Как ленивость реализована и сделана так, чтобы это не чрезвычайно медленнее/использовало больше пространства?

20
задан Earlz 27 January 2010 в 23:42
поделиться

4 ответа

Это относится к оценке синтаксического дерева. Если вы оцениваете синтаксическое дерево лениво (то есть, когда требуется значение, необходимое), вы должны нести его через предыдущие этапы вашего вычисления в полном объеме. Это накладные расходы ленивых оценок. Тем не менее, есть два преимущества. 1) Вы не будете двигаться на дереве без необходимости, если результат никогда не используется, и 2) вы можете выразить и использовать бесконечное синтаксическое дерево в некоторой рекурсивной форме, поскольку вы когда-либо оценили его только на необходимую глубину, а не только оценить (с нетерпением) в полном объеме, что было бы невозможно.

Я не знаю haskel, но, насколько я знаю языки, такие как Python или ML, оценивают нетерпеливо. Например, чтобы имитировать ленивую оценку в ML, вы должны создать фиктивную функцию, которая не принимает никаких параметров, но возвращает результат. Эта функция затем находится ваше синтаксическое дерево, которое вы можете лечить в любое время, вызвав его с помощью пустой списка аргументов.

9
ответ дан 30 November 2019 в 00:19
поделиться

Ленивая оценка может быть довольно полезной при создании структур данных с эффективными амортизированными границами.

Просто чтобы привести пример, вот неизменный класс стека:

class Stack<T>
{
    public static readonly Stack<T> Empty = new Stack<T>();
    public T Head { get; private set; }
    public Stack<T> Tail { get; private set; }
    public bool IsEmpty { get; private set; }

    private Stack(T head, Stack<T> tail)
    {
        this.Head = head;
        this.Tail = tail;
        this.IsEmpty = false;
    }

    private Stack()
    {
        this.Head = default(T);
        this.Tail = null;
        this.IsEmpty = true;
    }

    public Stack<T> AddFront(T value)
    {
        return new Stack<T>(value, this);
    }

    public Stack<T> AddRear(T value)
    {
        return this.IsEmpty
            ? new Stack<T>(value, this)
            : new Stack<T>(this.Head, this.Tail.AddRear(value));
    }
}

Вы можете добавить элемент на переднюю часть стека в o (1) время, но это требует (n) Время добавления предмета к задней части. Поскольку мы должны повторно создать всю нашущую структуру данных, AddRear - это монолитность .

Вот тот же самый неизменный стек, но теперь его лениво оценивается:

class LazyStack<T>
{
    public static readonly LazyStack<T> Empty = new LazyStack<T>();

    readonly Lazy<LazyStack<T>> innerTail;
    public T Head { get; private set; }
    public LazyStack<T> Tail { get { return innerTail.Value; } }
    public bool IsEmpty { get; private set; }

    private LazyStack(T head, Lazy<LazyStack<T>> tail)
    {
        this.Head = head;
        this.innerTail = tail;
        this.IsEmpty = false;
    }

    private LazyStack()
    {
        this.Head = default(T);
        this.innerTail = null;
        this.IsEmpty = true;
    }

    public LazyStack<T> AddFront(T value)
    {
        return new LazyStack<T>(value, new Lazy<LazyStack<T>>(() => this, true));
    }

    public LazyStack<T> AddRear(T value)
    {
        return this.IsEmpty
            ? new LazyStack<T>(value, new Lazy<LazyStack<T>>(() => this, true))
            : new LazyStack<T>(this.Head, new Lazy<LazyStack<T>>(() => this.Tail.AddRear(value), true));
    }
}

Теперь функция AddRear четко работает в o (1) . Когда мы получаем доступ к свойству хвоста , он будет оценить ленивое значение , достаточно просто , чтобы вернуть узлу головы, затем он останавливается, поэтому его больше не монолитная функция.

14
ответ дан 30 November 2019 в 00:19
поделиться

Используйте встроенные классы (я не занимаюсь использованием FlexMock или Stubba / Mocha, просто чтобы показать точку)

def test_should_callout_to_foo
   m = Class.new do
     include ModuleUnderTest
     def foo
        3
     end
   end.new
   assert_equal 6, m.foo_multiplied_by_two
 end

Любая библиотека издевательства / потубления должна дать вам более чистый способ сделать это. Также вы можете использовать структуры:

 instance = Struct.new(:foo).new
 class<<instance
     include ModuleUnderTest
 end
 instance.foo = 4

Если у меня есть модуль, который используется во многих местах, у меня есть тест подразделения для него, который делает только что (сдвиньте тестовый объект в методах модуля и тестируйте, если модульные методы функционируют правильно этот объект).

-121--1089034-

В Ruby мы используем модификаторы функций, обычно называемые «один раз», чтобы обернуть метод, чтобы он проводил ленивую оценку. Такой метод будет оценен только один раз, только кэшировал значение, последующие вызовы, возвращающие это значение.

Одно использование (или неправильное использование) ленивой оценки - сделать порядок инициализации объекта неявным, а не явным. До:

def initialize
  setup_logging  # Must come before setup_database
  setup_database  # Must come before get_addresses
  setup_address_correction  # Must come before get_addresses
  get_addresses
end

def setup_logging
  @log = Log.new
end

def setup_database
  @db = Db.new(@log)
end

def setup_address_correction
  @address_correction = AddressCorrection.new
end

def get_addresses
  @log.puts ("Querying addresses")
  @addresses = @address_correction.correct(query_addresses(@db))
end

с ленивой оценкой:

def initialize
  get_addresses
end

def log
  Log.new
end
once :log

def db
  Db.new(log)
end
once :db

def address_corrector
  AddressCorrection.new
end
once :address_corrector

def get_addresses
  log.puts ("Querying addresses")
  @addresses = address_corrector.correct(query_addresses(db))
end

Порядок инициализации различных взаимозависимых объектов теперь (1) неявный, а (2) автоматический. На нижнем пасту, поток контроля может быть непрозрачным, если этот трюк слишком сильно полагается.

1
ответ дан 30 November 2019 в 00:19
поделиться

Ленивая оценка - это общее свойство чисто функциональных языков программирования для "отыгрывания производительности", оно работает довольно просто, вы оцениваете выражение только тогда, когда оно вам нужно. Например, в Haskell

if x == 1 then x + 3 else x + 2

В строгой (нетерпеливой) оценке, если x действительно равно двум, то x + 3 оценивается и возвращается, иначе x + 2, в Haskell такого не происходит, x + 3 просто составляется в выражение, например, допустим, у меня есть:

let x = if x == 1 then x + 3 else x + 2

Хорошо, тогда я храню это в переменной, но что если я никогда не использую эту переменную из-за некоторых других условий? Я потратил очень дорогое целочисленное сложение на моем процессоре впустую. (ладно, на практике вы не выиграете на этом, но вы поняли идею с большими выражениями)

Тогда возникает вопрос, почему не все языки ленивы? Ну, простая причина в том, что в чисто функциональных языках выражения гарантированно не имеют побочных эффектов вообще. Если бы они были, нам пришлось бы оценивать их в правильном порядке. Вот почему в большинстве языков они оцениваются с нетерпением. В языках, где у выражений нет побочных эффектов, ленивая оценка не несет никакого риска, поэтому это логичный выбор, чтобы отыграть производительность, которую они теряют в других областях.

Еще один интересный побочный эффект заключается в том, что if-then-else в Haskell на самом деле является функцией типа Bool -> a -> a -> a. В Haskell это означает, что она принимает один аргумент типа Boolean, другой - любого типа, третий - того же типа, что и первый, и снова возвращает этот тип. Вы не столкнетесь с бесконечной оценкой различных ветвей управления, потому что значения оцениваются только тогда, когда они необходимы, что обычно происходит в самом конце программы, когда огромное выражение было составлено и затем оценивается для конечного результата, отбрасывая все, что, по мнению компилятора, не нужно для конечного результата. Например, если я делю очень сложное выражение на само себя, его можно просто заменить на '1', не оценивая обе части.

Разница видна в Scheme, которая традиционно строго оценивается, но есть ленивый вариант, называемый Lazy Scheme, в Scheme (display (apply if (> x y) "x больше y" "x не больше y")) - это ошибка, потому что if не является функцией, это специализированный синтаксис (хотя некоторые говорят, что синтаксис в Scheme вообще не является специализированным), поскольку он не обязательно оценивает все свои аргументы, иначе мы бы исчерпали память, если бы попытались вычислить, например, факториал. В Lazy Scheme это работает просто отлично, потому что ни один из этих аргументов не оценивается вообще, пока функции действительно не понадобится результат для продолжения оценки, как, например, в display.

10
ответ дан 30 November 2019 в 00:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: