Особые варианты использования замыканий: зачем использовать замыкания?

Обновлять

Я разрабатываю экспериментальный язык программирования, и вопрос в том, включать ли замыкания или просто использовать функции первого класса. Чтобы решить это, мне нужны реалистичные варианты использования / примеры, которые показывают преимущество замыканий над первоклассными функциями. Я знаю, что вы можете достичь всего, чего можете достичь с помощью одного из двух без них, но есть несколько вариантов использования, например, первоклассные функции, код которых легче читать (например, короче или не разбит на несколько классов). Например:

Рубин:

[1,5,7].map{|x| x*x }
[1,'test',3].select{|x| x.kind_of? Integer}.map{|x| x.to_s }
big_array.each{ |item| puts item }

Без функций первого класса эти примеры были бы намного более подробными, поскольку вам пришлось бы использовать циклы for или подобные вещи.

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

Исходный пост

Я не понимаю, почему замыкания привязаны к переменным, а не только к значениям, например:

Рубин:

x = 5
l = lambda { x }
l.call #=> 5
x = 100
l.call #=> 100

Какая польза от ссылки на переменные вместо простой ссылки на значения, хранящиеся в переменных в точке определения закрытия? Как в этом примере:

Рубин:

x = 5
l = lambda { x }
l.call #=> 5
x = 100
l.call #=> 5, not 100

Есть ли хорошие варианты использования, когда необходимо ссылаться на переменные, а не только на значения этих переменных в точке определения закрытия?


person Ragmaanir    schedule 18.10.2010    source источник


Ответы (2)


Весь смысл закрытия в том, что состояние закрытия может измениться. Возможно, закрывать что-то, что не (или не может) измениться, - это плохой тон, потому что вы скрываете параметр.

Возьмем следующий надуманный пример на C #:

var sb=new StringBuilder();
int counter = 0;
var Append=(int a, string s)=>
            sb.Append(a*a + counter++, SomethingElse(s, "some constant"));

Append закрывается над StringBuilder и счетчиком, так что я могу добавлять вызовы к нему через свой код, вместо того, чтобы каждый раз проходить через церемонию SomethingElse и sb.Append. Замыкания были бы менее полезны для такого рода вещей, если бы состояние внутри них не могло измениться.

person Dan Davies Brackett    schedule 18.10.2010
comment
Я думаю, вы меня неправильно поняли (или я неправильно понял ваш ответ: P): Посмотрите на первый опубликованный мной пример. Я присваиваю новое значение переменной x, которая определена вне замыкания. Хотя это присвоение происходит после закрытия, оно использует новое значение x (100 вместо 5). Почему замыкание не использует просто значение, присвоенное x в точке, где оно определено? - person Ragmaanir; 19.10.2010
comment
@Ragmaanir, потому что в этом весь смысл закрытия! если бы он использовал значение, которое переменная имела при определении, тогда он имел бы то же значение, что и параметр. Если это именно то поведение, которое вы хотите, передайте значение в качестве параметра, а не закрывайте переменную. - person Dan Davies Brackett; 19.10.2010
comment
@DDDaviesBrackett: Я не понимаю, как это будет то же самое, что и параметр. ИМО это было бы похоже на константу, потому что вы не можете заменить значение при вызове закрытия. Однако параметры можно передавать при каждом вызове. Причина не использования параметров заключается в том, что вы должны передавать их при каждом вызове. Но в каких случаях необходимо, чтобы замыкание распознавало переназначения переменной после закрытия? В вашем примере это используется (только) для переменной счетчика, я думаю. Возможно, такое поведение предназначено для неизменяемых значений (например, int)? - person Ragmaanir; 19.10.2010
comment
@Ragmaanir: DDaviesBrackett верен - захват (то есть закрытие) переменных - это весь смысл закрытия. Похоже, вам действительно нужно частичное приложение функции, которое иногда называют сокращение бета-версии, а иногда и неправильно называется каррированием. - person Daniel Pryden; 15.12.2010
comment
@Daniel Pryden: Мой первый комментарий был сформулирован неправильно, извините. Я знаю, что закрытие переменных - это точка закрытия. Вот почему я обновил свой вопрос: есть два типа закрытий: проще реализовать тип, закрывающий переменные, без отслеживания переназначений, сделанных для переменных, и сложнее реализовать тип закрытия, который отслеживает переназначения для используемых переменных в закрытии даже после закрытия. Какова цель последнего? Стоит ли включать в прогу такой тип замыкания. язык? - person Ragmaanir; 16.12.2010
comment
@Ragmaanir: вы можете построить систему закрытия из объектов или создать Система OO вне закрытия. Между ними есть удивительная симметрия. Возможно, это просветит вас. - person Daniel Pryden; 16.12.2010

Насколько я понимаю (кто-нибудь, пожалуйста, помогите мне, если я ошибаюсь), использование переменной в лямбда-выражении изменяет переменную (скажем, «Foo as Widget») на нового держателя (виджета), определенного, как показано ниже, и меняет все ссылки на Foo превращаются в ссылки на Foo.Value. Затем держатели для закрытых переменных передаются в качестве параметров в фабрику делегатов.

Class Holder(Of T)
  Public Value As T

  Sub New(NewValue As T)
    Value = NewValue
  End Sub
End Class

Я не совсем уверен, как часто дополнительный слой обертывания действительно полезен. Я бы подумал, что более распространенным является захват локальных переменных по значению. В моем собственном коде, прежде чем я обновился до версии VB, поддерживающей лямбды, я использовал фабрики делегатов, например

  Sub SetText(ByVal St As String)
    Text = St
  End Sub

  ... elsewhere
    MyControl.BeginInvoke(NewInv(AddressOf SetText, someExpression))

Обратите внимание, что someExpression будет вычисляться при выполнении самого BeginInvoke, а не при фактическом выполнении отложенного действия. Также обратите внимание, что мое семейство функций NewInv () поддерживает общие переопределения 1–4 параметров, и что, если требуется Action (Of T), а не MethodInvoker, у меня есть общий статический класс с другим семейством функций для этого.

Кстати, одно из преимуществ этого подхода: он работает с редактированием / продолжением.

person supercat    schedule 30.10.2010