F # Закрытие
У кого-нибудь есть достойный пример, желательно практический/полезный, они могут опубликовать демонстрацию концепции?
Ответы
Ответ 1
Закрытие может использоваться по ряду причин, одна из которых заключается в уменьшении поддержки вспомогательных функций или значений. Поэтому, вместо того, чтобы загрязнять модуль/пространство имен случайным дерьмом, вы можете охватить их там, где они необходимы.
open System
let isStrongPassword password =
let isLongEnough (str : string) = str.Length > 10
let containsNumber str =
str |> Seq.tryfind (fun c -> Char.IsDigit(c)) |> Option.is_some
let containsUpper str =
str |> Seq.tryfind (fun c -> Char.IsUpper(c)) |> Option.is_some
if isLongEnough password &&
containsNumber password &&
containsUpper password then
true
else
false
Изменить:
Вот еще один пример, который фиксирует значения и выводит их во внутреннюю область без передачи в качестве параметров.
#light
open System
let isPasswordStrongerThan myPassword yourPassword =
let mineIsLongerThan (x : string) =
(myPassword.Length > x.Length)
let mineHasMoreNumsThan (x : string) =
let numDigits (x : string) =
x
|> Seq.map (Char.IsDigit)
|> Seq.fold (+) 0
(numDigits myPassword > numDigits x)
if mineIsLongerThan yourPassword && mineHasMoreNumsThan yourPassword then
true
else
false
В примере "myPassword" используется во всех внутренних функциях, но он не передавался как параметр.
Ответ 2
Закрытие полезно для кеширования и memoization. Например:
let isWord (words: string list) =
let wordTable = Set.Create(words)
fun w -> wordTable.Contains(w)
> let isCapital = isWord ["London";"Paris";"Warsaw";"Tokyo"];;
val isCapital : (string -> bool)
> isCapital "Paris";;
val it : bool = true
> isCapital "Manchester";;
val it : bool = false
Обратите внимание, что wordTable
вычисляется только один раз, когда создается замыкание.
В общем, закрытие полезно для сохранения частного состояния. Следовательно, вы даже можете воссоздать cons-ячейки исключительно с помощью функций.
let cons a b = function
| true -> a
| false -> b
let car p = p true
let cdr p = p false
> (car (cons 1 2))
val it : int = 1
> (cdr (cons 1 2))
val it : int = 2
Ну, здесь cons-клетки неизменяемы. Но вы могли бы представить себе тоже изменчивое состояние. Позвольте сделать небольшой счетчик:
let makeCounter() =
let x = ref 0
let tick() =
x := !x + 1
!x
tick
let c1 = makeCounter()
let c2 = makeCounter()
> c1()
val it : int = 1
> c1()
val it : int = 2
> c2()
val it : int = 1
Известно, что закрытие - это объекты с бедными людьми, потому что объекты являются закрытыми людьми. Вы можете имитировать одно с другим.
Ответ 3
Я тоже боролся с этим - увидев слово "закрытие", брошенное экспертами F #. Это похоже на "область видимости" (и вложенную область), которые знакомы, но на самом деле это мало связано с этим и имеет значение только в коде, который передает функции как значения вне исходной области.
@Robert имеет хорошую цитату...
Закрытие - это функции, которые несут вокруг некоторой "среды", в которой они были определены. В частности, замыкание может ссылаться на переменные, которые были доступны в точке его определения.
Лучший пример, который я видел...
let Counter =
let count = ref 0
// *Return a function that carries the current context* (ie. "count", on
// the heap). Each time it is called, it will reference, and increment,
// the same location
(fun () -> incr count; !count)
> Counter()
val it : int = 1
> Counter()
val it : int = 2 <-- !! The anonymous function has retained the context
Это работает только потому, что count является переменной "ref" (т.е. в куче) и потому, что счетчик
возвращает функцию (а не целое число)
Далее идет неправильный путь - использование стека вместо кучи...
let Counter2 =
let mutable count = 0
// Attempt to reference mutable variable, from within a closure, is invalid
(fun () -> count <- count+1; count)
Это дает очень полезное сообщение об ошибке, которое говорит нам о закрытии.
Измененная переменная 'count' используется недопустимым образом. Переменные переменные не могут быть зафиксированы закрытием. Рассмотрим устранение этого использование мутации или с использованием динамической памяти выделяется изменяемый ссылочную ячейку с помощью "исх" и "!"
(похвально автору этого сообщения!)
Syme и др., "Эксперт F #" говорит, об использовании замыканий...
Это мощный метод скрытия и инкапсулирования изменяемого состояния без использования для написания новых типов и определений классов. Хорошая практика программирования в полированном коде обеспечить, чтобы все связанные элементы изменчивого состояния были собраны в рамках определенной структуры данных или другой объект, такой как функция.
Ответ 4
@Alex - это просто область функций. на этом все-таки закрываются.
@ESV -
(fun c...
образуют замыкание - даже если оно применяется непосредственно и не проходит. Я бы не сказал, что это отличный пример. Что-то, проще и прямо:
let derivative dx =
(fun f x -> (f (x+dx)) - (f x) / dx)
Мы возвращаем среду (dx) и анонимную функцию. Теперь вам необязательно возвращать функцию, например, пример Криса. Но это всегда связано с использованием более широко распространенных (или связанных) переменных - среды - в локальном контексте.
let filter lst f_critera =
let rec loop_ = function
| hd :: tl ->
if f_critera hd then hd :: (loop_ tl)
else (loop_tl)
| [] -> []
in
loop_ lst
Итак, это закрытие, хотя я его немного загнал, но для определения их это достойная рамка. f_criteria
связан, внутри loop_
- это переменная среды.
Ответ 5
Первый пример Криса Смита предлагает полезный проблеск область идентификатора в F #. Однако он не использует преимущества связанных переменных, доступных для вложенных функций. В следующем варианте minLength доступен для внутренней функции, потому что это замыкание.
open System.Text.RegularExpressions
let isStrongPassword minLength =
fun (password : string) ->
password.Length >= minLength &&
Regex.IsMatch(password, "\\d") &&
Regex.IsMatch(password, "[A-Z]")
Это тривиальное использование закрытия, потому что вы можете выполнить одно и то же currying в F #.
Ответ 6
В тексте Функциональное программирование существует такое определение:
Закрытие - это функции, которые несут вокруг некоторой "среды", в которой они были определены. В частности, замыкание может ссылаться на переменные, которые были доступны в точке его определения.
Это определение, вероятно, не является полным, но легко понять для кого-то из императивного/объективного языка.
Ответ 7
Здесь действительно простой пример
let rememberTheEvens =
let theList = List<int>()
fun n ->
if n%2 = 0 then theList.Add(n)
theList
И вот правильная запись, которую я сделал некоторое время назад, которая использует закрытие для Memoization
http://www.devjoy.com/2013/05/learning-to-think-functionally-memoization/
Надеюсь, что это поможет.
Ответ 8
ESV - как я понимаю (что также ограничено перспективой OO), закрытие связано с ограничением ваших функций (например, isLongEnough) в рамках метода, который их использует. Это эффективно закрывает (следовательно, закрытие термина) эти функции от остальной части вашего кода приложения.
Я думаю, что понимаю, что правильно, если не надеюсь, что я тоже стану правильным;)
Ответ 9
@ESV, замыкания представляют собой три определения let
, которые отступают (т.е. isLongEnough
, containsNumber
и containsUpper
). Они являются замыканиями, потому что они являются функциями, определенными в рамках функции isStrongPassword
.
Ответ 10
Закрытие формируется при фиксированных свободных переменных функции. Это похоже на метод класса, связанный с экземпляром объекта и ссылающийся на переменные-члены.
Свободные переменные - это переменные, которые не передаются функции как параметры, т.е. выбираются из среды, где определена функция.
Закрытия наиболее полезны для обратных вызовов и обработчиков событий, поскольку они позволяют сформировать временный объект без объявления его. Это огромный бонус функционального программирования над ООП.
Обратите внимание, что currying делает аналогичную вещь, но это другое понятие: оно фиксирует некоторые связанные переменные (т.е. параметры функции). Напротив, замыкание фиксирует свободные переменные.