The let construct was introduced by the committee working on assertions. They wanted functions that had arguments that did not necessarily have a data type, like passing
@(posedge clk), not just clk. They wanted other features that essentially made the construct expand like a text macro. But the problem with a text macro is that it has global compilation unit scope, and if you redefine a macros, it replaces the old definition. A function, on the other hand, has a local scope.
So they came up with the let construct to more formally represent a symbol for an arbitrary expression.