2014-07-17 14 views
6

W języku F # czy jest jakaś różnica między let f = fun a -> a-1 a let f a = a-1? O ile widzę, ten ostatni jest po prostu cukrem syntaktycznym dla tego pierwszego. Czy to jest poprawne?Różnica między `let f = fun a -> a-1` i` let f a = a-1` w F #

Poszukuję tutaj konkretnie różnic w postaci semantycznej, a nie różnic w tym, jak dany kompilator obsługuje te dwa przypadki.

Odpowiedz

7

(fun a -> ...) to po prostu funkcja anonimowa. F # różni się od C# tym, że funkcje są pierwszorzędnymi obywatelami, więc po powiązaniu anonimowej funkcji z f, da to f podpis typu val f : int -> int (ponieważ a jest wywnioskowany jako int32), tak jakbyś związał normalny, nazwany działaj jak w drugim przykładzie. Możesz udowodnić, że są semantycznie identyczne, uruchamiając swoje przykłady w języku F # interaktywnym.

Edit: Funkcje anonimowych nawet wspierać rodzajowych, które są zwykle wywnioskować, ale można je wyraźnie, jak to:

let f<'T> = (fun (x: 'T) -> x)

Edycja 2: od F # 3.1 specyfikacji projektu (found here):

Definicja wartości jest uważana za definicję funkcji, jeśli jej bezpośrednia prawa strona jest anonimową funkcją, jak w tym przykładzie: let f = (fun w -> x + w)

Odkładając oczywisty błąd składni na bok, chodzi o to, że wiąże się wartość funkcji (tj. funkcja anonimowa) do identyfikatora jest dosłownie odpowiednikiem normalnej definicji funkcji. Mówi się, że ta równoważność jest "podstawą programowania funkcjonalnego", więc możesz być pewny, że to się nie zmieni w przyszłości.

Dla celów ogólnych, F # traktuje funkcjonować definicje i wartości funkcji (tj instancji delegata funkcje anonimowe) jako równe w czasie projektowania, ale kiedy trzeba podnieść definicję funkcji do wartości funkcji, użyje delegat typu FSharpFunc jako typ kompilowany. Dzieje się tak w przypadku wszystkich funkcji wyższego rzędu, takich jak te w modułach Array, List, Seq i tak dalej, ponieważ nie ma rzeczywistego sposobu użycia metody CIL jako wartości funkcji, podobnie jak w przypadku delegatów. Wszystko inne wygląda dokładnie tak, jakbyś oczekiwał skompilowanego C# lub VB.NET do - to tylko delegaci, dla których F # używa FSharpFunc.

Edycja 3: Nie mogę jeszcze dodawać komentarzy, ale jeśli chodzi o odpowiedź Tomasa, kompilator F # nie wie, jak uogólnić wyrażenie let h =(); fun a -> a, ale zaakceptuje je, jeśli sam doda adnotacje typu , podobnie jak w przykładzie Nikona id.

Edytuj 4: Oto niesamowicie surowy obraz kompilacji funkcji F #. Zauważ, że przykład Tomasa, wyrażenie sekwencyjne, jak go nazwał, zmienia się w FSharpFunc, podczas gdy równoważna funkcja bez (); staje się rzeczywistą metodą CIL. Właśnie o tym mówiła specyfikacja F #. Ponadto, gdy używasz zwykłej metody CIL jako wartości, przy częściowym zastosowaniu lub w inny sposób, kompilator stworzy FSharpFunc, który będzie go reprezentował jako zamknięcie. enter image description here

+0

Dziękuję, to jest wyjątkowo wszechstronne! Dokładnie tekst z gwarancją, której szukałem. Nie znam kultury społeczności związanej z tą akcją, ale uważam, że twoja odpowiedź jest teraz lepsza niż odpowiedź @ TomasPetricek, więc zaakceptowałem ją. – Rhyme

5

Podane przykłady są semantycznie takie same, ale kompilator F # ma z tym wszystkim do czynienia.wygląd

Miejmy na innej funkcji (ogólne):

// val singleton1 : x:'a -> List<'a> 
let singleton1 x = [x] 

// val singleton2 : x:'a -> List<'a> 
let singleton2 = fun x -> [x] 

Jak widać, podpisy są takie same. Ale jeśli się nad tym zastanowić, to te dwie rzeczy naprawdę nie powinny być takie same: pierwsza to prawdziwa funkcja (kompiluje się do metody .NET), ale druga to po prostu wartość trzymająca funkcję (delegat lub Funk w C#). Ale w środowisku wykonawczym .NET nie ma żadnych ogólnych wartości. Działa to tylko dlatego, że kompilator F # jest wystarczająco inteligentny, aby uczynić także funkcję singleton2.

Można zobaczyć, co mam na myśli tutaj:

let singleton3 = id >> fun x -> [x] 

Teraz tu przechytrzył F # kompilatora i nie będzie to skompilować z powodu Value Restriction (przewiń do tematu), choć powinno być semantycznie tak samo jak singleton2.

Podsumowując: Z semantycznego punktu widzenia twoje definicje są takie same, ale z powodu ograniczeń w środowisku wykonawczym .NET, kompilator F # musi wykonać dodatkową pracę, aby to umożliwić.


Jedna inna różnica Właśnie sobie przypomniałem, że tylko funkcje mogą być oznaczone inline:

let inline f a = a-1 // OK 
let inline f = fun a -> a-1 // Error 
+2

Dla 'singleton3', podanie niezbędnych typów usuwa problem:' let singleton3 <'a> = id >> fun (x: 'a) -> [x] 'działa, jak @king jah wyjaśnia w innej odpowiedzi. Jeśli chodzi o rozróżnienie "true function" i "value holding a function", zrozumiałem, że F # używa "FSharpFunc <>" dla obu. Czy znasz źródło, które mówi inaczej? – Rhyme

+0

Masz rację. F # używa 'FSharpFunc <>' dla obu, i nie znam żadnego źródła, które by mówiło inaczej ... ale jeśli istnieje, Tomas będzie;) –

+0

Co ciekawe, 'let inline f <'a> = fun a - > a-1' działa! (podobnie jak "let inline h <'a> = 7"). Nie wiem dlaczego. Nie ma większego znaczenia semantycznego, aby je zaakceptować. 14.6.7 w przytoczonym przez @TomasPetricek wydaje się nie mieć zastosowania. Być może, jeśli podasz ten przykład jako oddzielne pytanie StackOverflow, ktoś mógłby pomóc nam zrozumieć to. Z tego co wiem, jest to po prostu dziura w specyfikacji lub problem z kompilatorem. – Rhyme

7

Jak inni już wspomniano, prosta odpowiedź jest, że definiowanie funkcji przy użyciu let foo x = ... i korzystania let foo = ... może dać inne wyniki (z powodu ograniczenia wartości), ale w tym przypadku nie ma to miejsca, ponieważ kompilator jest wystarczająco inteligentny, aby wiedzieć, że może bezpiecznie traktować let foo = fun x -> .. jako let foo x = ....

Teraz, jeśli chcesz zobaczyć więcej szczegółów, spróbuj się następujące definicje:

let f a = a    // Function 'a -> 'a 
let g = fun a -> a  // Function 'a -> 'a 
let h =(); fun a -> a // error FS0030: Value restriction 

Tak, kompilator traktuje funkcję zdefiniowaną za pomocą let foo = fun x -> ... jako zwykłą funkcję tak długo, jak nie ma nic innego przed kodem która tworzy & zwraca tę funkcję. Jeśli zrobisz coś więcej (nawet jeśli po prostu ignorujesz wartość jednostki), to jest różnica.

Jeśli chcesz dowiedzieć się więcej, przejdź do sekcji 14.6.7 Generalizacja w F # 3.0 Specyfikacja listy wszystkich uogólnić wartości (to znaczy, że wyrażenia, dla których wyżej prace):

się następujące wyrażenia uogólnić:

  • Wyrażenie funkcja
  • ekspresyjnym obiekt, który implementuje interfejs
  • Wyrażenie delegata
  • Wyrażenie definicyjne "let", w którym znajduje się prawostronna strona definicji i treść wyrażenia są generalizowane
  • Wyrażenie definicyjne "let rec", w którym prawa strona wszystkich definicji i treść wyrażenia są uogólnione
  • Wyrażenie krotkowe, którego wszystkie elementy są uogólnić 
  • rekord ekspresji, którego wszystkie elementy są uogólnić, gdzie rekord nie zawiera zmienne pola 
  • przypadku unia wyrażenie, którego wszystkie argumenty są uogólnić
  • wyrażenie wyjątek, którego wszystkie argumenty są generalizowalny
  • Puste wyrażenie tablicowe 
  • Stałe wyrażenie
  • Aplikacja funkcji typu, która ma atrybut GeneralizableValue.

Ważną rzeczą jest to, pierwszy punkt - że ekspresja funkcja (np fun x -> x) jest uogólnione; Wyrażenie sekwencjonowania (na przykład (); fun x -> x) nie podlega generalizacji i dlatego zachowuje się inaczej niż zwykła funkcja.