2010-01-30 4 views
8

So habe ich gerade mein erstes F # -Programm beendet, mit meinem einzigen funktionalen Hintergrund ein bisschen des Wissens von Haskell (lies: Habe wirklich keine Programme darin produziert).F #: Warum muss ich 'Einheit' explizit für Funktionen angeben, die keine Argumente haben?

Nach einigen boggling Verhalten erlebt, wurde mir klar, dass F # eine Unterscheidung zwischen macht:

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

und

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

bemerkte ich, dass es "Caches" die ehemalige, neu berechnet wird es nie, wenn es heißt wieder, während es letzteres wie eine normale Funktion behandelt. Sie können den Unterschied nicht feststellen, wenn die fragliche Funktion offenbar keine Nebenwirkungen hat, aber meine shuffle tat!

Sollte dies allgemein bekannt sein? Ich habe es noch nicht in irgendwelchen Lernmaterialien erwähnt. Ist der Grund nur eine Schwäche im Parser, ein bisschen wie Sie haben, um eine Funktion zu deklarieren, bevor Sie es verwenden?

Antwort

15

Die meisten F # Material tut erklären, dass alle Top-Level-Anweisungen in einem Modul von oben nach unten auf Deklaration ausgeführt werden. Mit anderen Worten, was Sie deklariert haben, ist keine Funktion, sondern ein Wert, der beim Ausführen des Programms einmal gebunden ist.

Es hilft wirklich, den reflektierten Code zu sehen. Ich habe eine einfache Datei:

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

Der kompilierte Code etwa wie folgt aussieht:

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

//... 

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

So ist eine statische Eigenschaft, die andere eine Funktion ist. Dies ist eine wünschenswerte Eigenschaft, weil sich vorstellen, wenn wir so etwas wie dieses hatte:

let x = someLongRunningDatabaseCall() 

Wir wollen nur x einmal gebunden sein, wir wollen es nicht Datenbank Funktion jedes Mal aufzurufen wir x zugreifen.

Zusätzlich können wir interessante Code wie folgt schreiben:

> 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 

Da isInNebraska ist ein Wert, dessen sofort ausgewertet. Es passiert einfach, dass sein Datentyp (string -> bool) ist, so dass es wie eine Funktion aussieht. Als Ergebnis füllen wir nur unsere cities Menge einmal, selbst wenn wir die Funktion 1000 mal aufrufen.

Lassen Sie uns diesen Code auf diese vergleichen:

> 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 

Hoppla, schaffen wir eine neue Städte setzen jedes Mal rufen wir die Funktion.

Also gibt es definitiv eine legitime und echte Unterscheidung zwischen Werten und Funktionen.

+1

Schönes Beispiel. Für alle Interessierten wird im Buch "Expert F #" (Kapitel 8) diese Diskussion im Zusammenhang mit dem Entwerfen von Funktionen für eine effiziente partielle Anwendung etwas weiter entwickelt. – itowlson

+1

Ich denke, es gibt hier einen Fehler: Es gibt nicht viel Unterschied zwischen einer statischen Eigenschaft und einer statischen Methode, da beide Methoden hinter den Kulissen sind, und ein Zugriff auf eine Eigenschaft ist eigentlich ein Methodenaufruf (deshalb können Sie markieren eine Eigenschaft als * virtuell *). Ich kann dein "wünschenswertes Eigentum" nicht sehen. –

+1

Ich glaube, dass der Unterschied darin besteht, dass, wenn Sie "julietNoFunction = ..." haben, die statische Eigenschaft einfach einen in einem statischen Feld gespeicherten Wert zurückgibt und dieser Wert nur einmal im statischen Konstruktor des generierten Typs berechnet wird Modul. Ich denke, ich habe das in Reflector gesehen, aber ich bin mir nicht mehr sicher. Wie auch immer, die Art, wie Sie es präsentieren, zeigt überhaupt keine Vorteile bei der Verwendung von 'let juliet = ...' über 'let juliet() = ...'. –

6

So funktioniert es in fast jeder Sprache mit Nebenwirkungen.

let name = expr 

führt den Code ‚jetzt‘ und kann Nebenwirkungen verursachen, wenn expr Wirkungen hat. Nachfolgende Verweise auf name haben keine Auswirkungen. Während

let name() = expr 

eine Funktion definiert, hat jetzt keine Auswirkungen, und bewerten (und haben Auswirkungen) name() jedes Mal aufgerufen wird.

+0

Verstanden. Ich glaube, ich war verwirrt, weil F # die erste Sprache ist, die ich benutzt habe, die funktional aber nebenwirkend ist. –

Verwandte Themen