Что преимущества там к Отложенным вычислениям в противоположность Нетерпеливой Оценке?
Что производительность наверху там? Отложенные вычисления собираются быть медленнее или быстрее? Почему (или это зависит от реализации?)?
Как отложенные вычисления на самом деле работают в большинстве реализаций? Мне казалось бы, что это будет намного медленнее и интенсивно использующим память, потому что переменные должны сохраненные операции, а также числа. таким образом, как это работает на языке как Haskell (примечание, я на самом деле не знаю тот язык)? Как ленивость реализована и сделана так, чтобы это не чрезвычайно медленнее/использовало больше пространства?
Это относится к оценке синтаксического дерева. Если вы оцениваете синтаксическое дерево лениво (то есть, когда требуется значение, необходимое), вы должны нести его через предыдущие этапы вашего вычисления в полном объеме. Это накладные расходы ленивых оценок. Тем не менее, есть два преимущества. 1) Вы не будете двигаться на дереве без необходимости, если результат никогда не используется, и 2) вы можете выразить и использовать бесконечное синтаксическое дерево в некоторой рекурсивной форме, поскольку вы когда-либо оценили его только на необходимую глубину, а не только оценить (с нетерпением) в полном объеме, что было бы невозможно.
Я не знаю haskel, но, насколько я знаю языки, такие как Python или ML, оценивают нетерпеливо. Например, чтобы имитировать ленивую оценку в ML, вы должны создать фиктивную функцию, которая не принимает никаких параметров, но возвращает результат. Эта функция затем находится ваше синтаксическое дерево, которое вы можете лечить в любое время, вызвав его с помощью пустой списка аргументов.
Ленивая оценка может быть довольно полезной при создании структур данных с эффективными амортизированными границами.
Просто чтобы привести пример, вот неизменный класс стека:
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)
. Когда мы получаем доступ к свойству хвоста
, он будет оценить ленивое значение , достаточно просто , чтобы вернуть узлу головы, затем он останавливается, поэтому его больше не монолитная функция.
Используйте встроенные классы (я не занимаюсь использованием 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) автоматический. На нижнем пасту, поток контроля может быть непрозрачным, если этот трюк слишком сильно полагается.
Ленивая оценка - это общее свойство чисто функциональных языков программирования для "отыгрывания производительности", оно работает довольно просто, вы оцениваете выражение только тогда, когда оно вам нужно. Например, в 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.