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