2010-01-30 14 views
8

Así que acabo de terminar mi primer programa F #, con mi único fondo funcional siendo un poco de conocimiento de Haskell (léase: Realmente no he producido ningún programa en él).F #: ¿Por qué debo especificar explícitamente 'unit' para las funciones que no toman argumentos?

Después de experimentar algún comportamiento sobresaltado, me di cuenta de que F # hace una diferenciación entre:

prepareDeck = allSuits |> List.collect generateCards |> shuffle 

y

prepareDeck() = allSuits |> List.collect generateCards |> shuffle 

me di cuenta de que "cachés" la antigua, no volver a calcular si se llama de nuevo, mientras que trata a este último como una función normal. No se puede notar la diferencia si la función en cuestión no tiene efectos secundarios, obviamente, ¡pero mi shuffle sí lo hizo!

¿Se suponía que esto era de dominio público? Aún no lo he visto mencionado en ningún material tutorial. ¿Es la razón solo una debilidad en el analizador, algo así como que tiene para declarar una función antes de usarla?

Respuesta

15

La mayoría del material F # does explica que todas las declaraciones de nivel superior en un módulo se ejecutan desde arriba hacia abajo en la declaración. En otras palabras, lo que has declarado no es una función, sino un valor que se vincula una vez cuando se ejecuta el programa.

Realmente ayuda a ver el código reflejado. Tengo un archivo simple:

let juliet = "awesome" 
let juliet2() = "awesome" 

El código compilado se ve algo como esto:

public static string juliet 
{ 
    [CompilerGenerated, DebuggerNonUserCode] 
    get 
    { 
     return "awesome"; 
    } 
} 

//... 

public static string juliet2() 
{ 
    return "awesome"; 
} 

Así que se trata de una propiedad estática, la otra es una función. Esta es una propiedad deseable, porque imaginar si tuviéramos algo como esto:

let x = someLongRunningDatabaseCall() 

única Queremos x en obligarse vez, no queremos que se invoque la función de base de datos cada vez que se accede a x.

Además, podemos escribir código interesante como esto:

> let isInNebraska = 
    printfn "Creating cities set" 
    let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"] 
    fun n -> cities.Contains(n);; 
Creating cities set 

val isInNebraska : (string -> bool) 

> isInNebraska "Omaha";; 
val it : bool = true 

> isInNebraska "Okaloosa";; 
val it : bool = false 

Desde isInNebraska es un valor, su evaluadas inmediatamente. Sucede que su tipo de datos es (string -> bool), por lo que parece como una función. Como resultado, solo llenamos nuestro conjunto cities una vez, incluso si invocamos la función 1000 veces.

Comparemos ese código para esto:

> let isInNebraska2 n = 
    printfn "Creating cities set" 
    let cities = set ["Omaha"; "Bellevue"; "Lincoln"; "Papillion"; "La Vista"; "Ralston"] 
    cities.Contains(n);; 

val isInNebraska2 : string -> bool 

> isInNebraska2 "Omaha";; 
Creating cities set 
val it : bool = true 

> isInNebraska2 "Okaloosa";; 
Creating cities set 
val it : bool = false 

Vaya, estamos creando una nueva ciudades para crear cada vez que se invoca la función.

Por lo tanto, definitivamente existe una distinción legítima y real entre valores y funciones.

+1

Buen ejemplo. Para cualquiera que esté interesado, el libro "Expert F #" (capítulo 8) desarrolla esta discusión un poco más en el contexto del diseño de funciones para una aplicación parcial eficiente. – itowlson

+1

Creo que hay un error aquí: no hay mucha diferencia entre una propiedad estática y un método estático en que ** ambos son métodos ** detrás de escena, y el acceso a una propiedad es en realidad una llamada a método (es por eso que puedes marcar una propiedad como * virtual *). No puedo ver tu "propiedad deseable". –

+1

Creo que la diferencia es que cuando tiene 'let julietNoFunction = ...' la propiedad estática simplemente devolverá un valor almacenado en un campo estático, y este valor se calcula solo una vez en el constructor estático del tipo generado para el módulo. Creo que he visto esto en Reflector, pero ya no estoy seguro. De todas formas, la forma en que lo presentas no muestra ninguna ventaja en el uso de 'let juliet = ...' sobre 'let juliet() = ...'. –

6

Así funcionan las cosas en prácticamente todos los idiomas con efectos secundarios.

let name = expr 

ejecuta el código 'ahora', y puede causar efectos secundarios si expr tiene efectos. Las referencias posteriores a name no tienen ningún efecto. Mientras que

let name() = expr 

define una función, no tiene efectos ahora, y evaluará (y tienen efectos) cada vez que se invoca name().

+0

Gotcha. Supongo que estaba confundido porque F # es el primer idioma que he usado que es funcional pero de efecto secundario. –

Cuestiones relacionadas