In the pure functional language Haskell, you could write this as
result =
let x = f y
in g x x
or
result = g x x
where x = f y
or, indeed, as
result = (\x -> g x x) (f y)
...if you really wanted to for some reason.
An imperative language is one where the program is organized as a series of instructions (verbs)—that is the literal meaning of the word imperative. For the most part, this is equivalent to saying an imperative language is one that allows repeated mutation of a variable.
By contrast, the above notations bind the variable x
once; it cannot be updated in-place. x
is a name for a shared subexpression, not a mutable memory location.
Imperative vs. functional is a matter of language semantics, not language syntax. If you take an imperative language and disallow reassignment of all variables (i.e. require programs to be written in static single assignment form) and mutation of data structures, you no longer have an imperative language; you have a first-order functional language with a somewhat inconvenient syntax.
To write a very imperative version of this using Haskell monads—that explicitly creates a mutable memory location, assigns it the value of f y
, reads it twice, and calls g
with these values—you'd have to write something like
result = runST $ do
x <- newSTRef (f y)
liftM2 g (readSTRef x) (readSTRef x)
It is worth mentioning that the history of the past 40 years or so of programming language design is dominated by designers trying new ways of hybridizing features of imperative and functional programming languages. That is why JavaScript has const-bindings and anonymous functions, Haskell has monads, etc.
let
-statements in SML, OCaml, or Haskell. $\endgroup$