Подтвердить что ты не робот

В чем преимущества ленивой оценки?

Какие преимущества существуют для "ленивой оценки", в отличие от "Оценки с нетерпением"?

Какие издержки производительности существуют? Будет ли ленивая оценка медленнее или быстрее? Почему (или зависит от реализации?)?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Чтобы привести пример, это непреложный класс стека:

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) время, но для добавления элемента в тылу требуется O(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) сейчас. Когда мы получим доступ к свойству Tail, он будет оценивать значение Lazy, достаточное для возврата головы node, затем оно останавливается, поэтому его больше не монолитная функция.

Ответ 3

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

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

Ответ 4

В 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) автоматический. С другой стороны, поток контроля может быть непрозрачным, если этот трюк слишком сильно полагается.