I understand the reasons why I would prefer let a = f(x)
over let a() = f(x)
, especially when f takes is a long running function.
I also think it is correct to say, that, considering lambda calculus origins of functional programming let a()
is truly functional, while let a
is not. Please correct me if I'm wrong on this.
However I have no clue which expression will actually be better:
let a = 4
or
let a() = 4
Is there any reason I would prefer one over the other?
Best Answer
There's nothing "unfunctional" about
let a = 4
as opposed tolet a () = 4
. In fact, in many abstract formulation of type theories, the latter is really syntactic sugar forlet a = fun () -> 4
.The main difference is when you want the expression to be evaluated, as you said. If
a
will eventually be evaluated anyway, there's no point in wrapping it into a function. OTOH if you think that there's a good chance thata
might not be evaluated, and the evaluation is very expensive (or may even fail if some precondition is not satisfied), then it might be beneficial to keep as a function. However, in the latter case you still have to be careful to not evaluate the function more than once.However, I'm not sure if pervasive use of laziness is all that common in a strict language such as F#.
Edit to address your question in the comments:
In many formal mathematical specifications of a language, the
let
construct binds an expression to a single variable:The reason is because this is simpler than having a
let
that binds arbitrary functions such aslet f x = 2 * x
. Instead, the latter is treated as a syntactic sugar for the more verboselet f = fun x -> 2 * x
.However, neither is more "functional" (if that word even has a well-defined meaning). Functional programming does not mean you should attempt to use functions everywhere possible. Rather, it merely puts functions on equal standing with other values so that you can manipulate both functions and values with equal ease.
The
let
construct in Haskell looks superficially like the one in F#, but semantically it is very different because it's non-strict ("lazy") by default. This meanslet x = 4 in 2 * x
in Haskell is more appropriately translated intolet x = lazy(4) in 2 * Lazy.force x
.The
let
construct should not be confused with assignments, which are an entirely different thing as it requires mutation. By constrast, let does not mutate anything: it merely introduces a new variable. F# blurs the boundary because it allows pure and impure functions to be mixed, solet
bindings can have side-effects as a result and hence the way you bind them (as functions or values) will matter.In a pure language,
let
bindings cannot cause any side-effects, but the order of evaluation still matters for two reasons:So even in pure languages, it still matters whether evaluation is done eagerly or lazily.