В F#:
> let f x = x + 2;;
val f : int -> int
> let g x = f x;;
val g : int -> int
> g 10;;
val it : int = 12
> let f x = x + 3;;
val f : int -> int
> g 10;;
val it : int = 12
В Clojure:
1:1 user=> (defn f [x] (+ x 2))
#'user/f
1:2 user=> (defn g [x] (f x))
#'user/g
1:3 user=> (g 10)
12
1:4 user=> (defn f [x] (+ x 3))
#'user/f
1:5 user=> (g 10)
13
Обратите внимание, что в Clojure новую версию f называют в последней строке. В F# однако все еще называют старую версию f. Почему это и как это работает?
Как сказал gabe , F # interactive использует затенение значений, когда вы вводите функцию с уже существующим именем (для получения дополнительной информации по слежению см., например, этот вопрос SO ). Это означает, что компилятор F # видит что-то вроде этого, когда вы запускаете свой код:
> let f@1 x = x + 2;;
> let g@1 x = f@1 x;;
> g@1 10;;
val it : int = 12
> let f@2 x = x + 3;;
> g@1 10;;
val it : int = 12
F # использует искаженное имя (например, @), которое вы не можете использовать напрямую, чтобы различать версии значения. С другой стороны, поведение Clojure, вероятно, лучше всего можно понять как большой словарь функций. Используя псевдосинтаксис, примерно так:
> symbols[f] = fun x -> x + 2;;
> symbols[g] = fun x -> symbols[f] x;;
> symbols[g] 10;;
val it : int = 12
> symbols[f] = fun x -> x + 3;;
> symbols[g] 10;;
val it : int = 13
Это должно прояснить различие.
В качестве примечания, есть одна возможная проблема с подходом Clojure (по крайней мере, для такого языка, как F #). Вы можете объявить функцию некоторого типа, использовать ее, а затем следующая команда может изменить тип функции. Если F # использует подход Clojure, как должен работать следующий пример?
> let f a b = a + b;;
> let g x = f x x;;
> let f () = printf "f!";;
> g 0;;
Функция g
использует f
, как если бы у нее было два параметра типа int
, но третья строка меняет тип функции. Это делает подход Clojure немного сложным для языков с проверкой типов.
В Clojure символ f
захватывает имя f
, а в F # - f
символ фиксирует значение f
. Таким образом, в Clojure каждый раз, когда вы вызываете g
, он ищет f
, чтобы узнать, к чему это имя относится в данный момент, тогда как в F # каждый вызов g
использует значение, которое f
имело при первоначальном создании функции g
.
Гейб и Томас хорошо изучили основы. Обратите внимание: если вы хотите, чтобы F # вел себя так же, как Clojure, вы можете использовать изменяемую привязку и переназначить f
:
let mutable f = fun x -> x + 2
let g x = f x
g 10;; // 12
f <- fun x -> x + 3 // note, assign new value, don't create new binding
g 10;; //13