son que programming programacion los functional closure f# functional-programming closures

f# - que - closures go



F#Cierre (10)

Alguien tiene un ejemplo decente, preferiblemente práctico / útil, ¿podrían publicar demostrando el concepto?


@Alex: ese es solo el alcance de las funciones. cierres basados ​​en eso sin embargo.

@ESV -

la (fun c... forma un cierre, aunque se aplica directamente y no se transmite. No diría que es un gran ejemplo. Algo más simple y directo:

let derivative dx = (fun f x -> (f (x+dx)) - (f x) / dx)

Estamos devolviendo un entorno (dx) y la función anónima. Ahora, no necesariamente tiene que devolver una función, como el ejemplo de Chris. Pero siempre tiene que ver con el uso de variables de ámbito más amplio (o vinculadas), el entorno, dentro de un contexto local.

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

Entonces, este es un cierre, aunque lo forcé un poco, pero para definirlos es un marco decente. f_criteria está vinculado, dentro de loop_ - es la variable de entorno.


@ESV, los cierres son las tres definiciones de let que están indentadas (es decir, isLongEnough , containsNumber y containsUpper ). Son cierres porque son funciones definidas dentro del alcance de la función isStrongPassword .



ESV: tal como lo entiendo (que también está limitado por una perspectiva OO) el cierre tiene que ver con la limitación de sus funciones (como isLongEnough) dentro del alcance del método que las usa. Esto efectivamente cierra (de ahí el término cierre) estas funciones se desactivan del resto del código de su aplicación.

Creo que lo estoy entendiendo bien, si no espero, también voy a aclararme;)


El cierre se forma cuando las variables libres de una función son fijas. Esto es similar a un método de clase que está vinculado a una instancia de objeto y se refiere a las variables de miembro.

Las variables libres son variables que no se pasan a la función como parámetros, es decir, se recogen del entorno donde se define la función.

Los cierres son más útiles para call-backs y manejadores de eventos, ya que permiten formar un objeto temporal sin declarar uno. Esta es una gran ventaja de la programación funcional sobre OOP.

Tenga en cuenta que currying hace algo similar, pero es un concepto diferente: está fijando algunas variables vinculadas (es decir, parámetros de función). Por el contrario, el cierre arregla variables libres.


El primer ejemplo de Chris Smith ofrece una visión útil del alcance del identificador en F # . Sin embargo, no aprovecha las variables vinculadas disponibles para las funciones anidadas. En la siguiente variación, minLength está disponible para la función interna porque es un cierre.

open System.Text.RegularExpressions let isStrongPassword minLength = fun (password : string) -> password.Length >= minLength && Regex.IsMatch(password, "//d") && Regex.IsMatch(password, "[A-Z]")

Este es un uso trivial de un cierre porque puedes lograr lo mismo al currificar en F #.


En el texto Programación Funcional hay esta definición:

Los cierres son funciones que llevan a cabo algo del "entorno" en el que fueron definidas. En particular, un cierre puede hacer referencia a variables que estaban disponibles en el punto de su definición.

Esta definición probablemente no es completa, pero es fácil de comprender para alguien del lenguaje imperativo / objetivo.


He luchado con esto también, viendo la palabra "cierre" que los expertos de F # han lanzado. Suena como "alcance" (y alcance anidado) que son familiares, pero en realidad tiene poco que ver con eso, y solo es relevante en el código que está pasando alrededor de funciones como valores fuera del alcance original .

@Robert tiene una buena cita ...

Los cierres son funciones que llevan a cabo algo del "entorno" en el que fueron definidas. En particular, un cierre puede hacer referencia a variables que estaban disponibles en el punto de su definición.

El mejor ejemplo que he visto ...

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

Esto solo funciona porque count es una variable "ref" (es decir, en el montón) y porque Counter devuelve una función (en lugar de un entero)

El siguiente paso es incorrecto: usar la pila en lugar del montón ...

let Counter2 = let mutable count = 0 // Attempt to reference mutable variable, from within a closure, is invalid (fun () -> count <- count+1; count)

Esto produce un mensaje de error muy útil, que nos dice mucho sobre cierres ...

La variable variable ''count'' se usa de forma inválida. Las variables mutables no pueden ser capturadas por cierres. Considere la posibilidad de eliminar este uso de la mutación o el uso de una celda de referencia mutable asignada en el montón a través de ''ref'' y ''!''

(felicitaciones al autor de ese mensaje!)

Syme, et al, "Expert F #" dice, sobre el uso de cierres ...

Esta es una poderosa técnica para ocultar y encapsular el estado mutable sin recurrir a la escritura de nuevas definiciones de tipos y clases. Es una buena práctica de programación en código pulido garantizar que todos los elementos relacionados de estado mutable se recopilen bajo alguna estructura de datos con nombre u otra entidad, como una función.


Los cierres se pueden utilizar por varias razones, una de las cuales es reducir el conjunto de funciones o valores auxiliares. Entonces, en lugar de contaminar el módulo / espacio de nombres con basura al azar, puedes ponerlos al alcance justo donde sean necesarios.

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

Editar: Aquí hay otro ejemplo que captura valores y los lleva a un alcance interno sin pasarlos como parámetros.

#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

En el ejemplo, ''myPassword'' se usa en todas las funciones internas, pero no se pasó como un parámetro.


Los cierres son útiles para el almacenamiento en memoria caché y la memorización. Por ejemplo:

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

Observe cómo la wordTable se calcula solo una vez, cuando se crea el cierre.

De manera más general, los cierres son útiles para mantener un estado privado. Por lo tanto, incluso puede recrear células de cons puramente con funciones.

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

Bueno, aquí las células cons son inmutables. Pero podrías imaginar tener un estado mutable también. Hagamos un pequeño contador:

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

Se sabe que los cierres son objetos de un pobre, porque los objetos son cierres de un pobre :) Puedes simular uno con el otro.