Ответ 1
let f x = fun y -> x + y
let g x y = x + y
Анализ этих определений функций в dnSpy для оптимизированной сборки показывает, что они:
public static int f(int x, int y)
{
return x + y;
}
public static int g(int x, int y)
{
return x + y;
}
Это не так уж странно, потому что g
на самом деле является кратким определением для f
что является общим случаем. В языках F # -like функции концептуально всегда принимают одно значение, возвращающее одно значение. Значения могут быть функциями. Это легче увидеть, если один из них является функцией подписи для f
и g
val f: int -> int -> int
// Actually is
// val f: int -> (int -> int)
// ie f is a function that takes a single int and returns a function that takes a single int and returns an int.
Чтобы заставить F # выполняться быстрее в .NET, физическое представление f
в сборке:
public static int f(int x, int y)
Пока это более естественное представление функции F #.
public static Func<int, int> f(int x)
Будет плохо, хотя.
Обычно F # достаточно умен, чтобы избежать издержек абстракции путем оптимизации, как описано выше, и при вызове. Однако есть ситуации, когда F # не может оптимизировать для вас.
Представьте, что вы реализуете fold
let rec fold f s vs =
match vs with
| v::vs -> fold f (f s v) vs
| [] -> s
Здесь F # не может полностью оптимизировать fsv
. Причина в том, что f
может иметь более сложную реализацию, чем приведенная выше, которая может возвращать другую функцию в зависимости от s
.
Если вы посмотрите в dnSpy
то заметите, что F # вызывает функцию, используя InvokeFast
но это делает внутренний тест, чтобы определить, можно ли ее быстро вызвать. Затем мы делаем этот тест для каждого значения, даже если это одна и та же функция.
По этой причине иногда можно увидеть fold
написанный так:
let fold f s vs =
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt f
let rec loop s vs =
match vs with
| v::vs -> loop (f.Invoke (s, v)) vs
| [] -> s
loop s vs
Adapt
здесь тесты перед циклом, если f
действительно может быть оптимизирован, а затем возвращает эффективный адаптер. В общем случае это все еще может быть немного медленнее, но тогда именно это и задумал вызывающий.
Заметка; это потенциальное снижение производительности не происходит для простых значений функций, таких как 'T → 'U
Это всегда может быть эффективно использовано.
Надеюсь это поможет.