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 # -подобных языках функции концептуально всегда принимают одно значение, возвращающее одно значение.Значения могут быть функциями.Это легче увидеть, если один из этих пунктов подписи функции для 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 # не может полностью оптимизировать f s v
.Причина в том, что 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
.Это всегда может быть эффективно использовано.
Надеюсь, это поможет.