2008-08-16 12 views
13

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

Respuesta

10

Los cierres se pueden utilizar por una serie de razones, una de las cuales es reducir el número 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: Así es otro ejemplo que captura los valores y los pone en un ámbito interno sin ser pasado 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.

+0

Una nota al margen: la última declaración 'if-then-else' es equivalente a' mineIsLongerThan yourPassword && mineHasMoreNumsThan yourPassword' –

2

El primer ejemplo de Chris Smith ofrece una visión útil de identifier scope in F#. Sin embargo, no aprovecha el bound variables disponible 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, ya que podría lograr lo mismo por currying en F #.

0

ESV - según tengo entendido (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 estoy entendiendo bien, si no Espero estar dos sets también;)

3

@Alex - eso es sólo el alcance de las funciones. cierres basados ​​en eso sin embargo.

@ESV -

la (fun c... forma un cierre --incluso aunque está siendo aplicada directamente y no pasa alrededor. 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 volviendo 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 

Por lo tanto, se trata de un cierre, aunque Forcé un poco, pero en aras de definirlos Es un marco decente. f_criteria está vinculado, dentro de loop_ - es la variable de entorno.

0

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

1

En el texto Functional Programming existe esta definición:

Los cierres son funciones que llevan alrededor de algunos de los "medio ambiente" en el que se han definido. 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.

8

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 

Aviso cómo el wordTable se calcula sólo una vez, cuando se crea el cierre.

En términos más generales, 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. Vamos a hacer un pequeño mostrador:

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 hombre pobre, porque los objetos son cierres de un hombre pobre :) Puede simular una con la otra.

4

He tenido problemas con esto también, viendo la palabra "clausura" lanzada por los expertos de F #. 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 por las funciones como valores fuera de alcance original.

@ Robert tiene una buena cita ...

cierres son funciones que llevan alrededor de algunos de los "medio ambiente" en el que se han definido. 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 sólo funciona porque recuento es una variable "ref" (es decir. En el montón), y debido a Contador devuelve una función (en vez de un entero)

el siguiente es el camino equivocado - el uso de 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 acerca de los cierres ...

La variable variable 'count' se utiliza de forma no válida. Las variables mutables no pueden ser capturadas por cierres. Considere eliminar este uso de mutación o usar 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, "Experto F #", dice, en el uso de cierres ...

Ésta es una técnica poderosa para esconderse y encapsular el estado mutable sin tener que recurrir a la escritura nuevo tipo de clase y las definiciones . Es una buena práctica de programación en el código pulido a asegurarse de que todos los elementos relacionados de estado mutable se recopilan bajo alguna estructura de datos con nombre o otra entidad como una función.

0

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 los call-backs y los 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 enlazadas (es decir, parámetros de función). Por el contrario, el cierre arregla variables libres.