In the Pascel/C/C++/etc… world a variable is a set of memory, it may or may not be changeable, but it is it a bit of memory able to be referenced and sized and all. Optimization passes in the compile may elide them sometimes, but for all intents and purposes you know it is a bit of memory, usually on the stack.
In the (more pure) functional world, like (mostly) on the EVM/BEAM, variables do not exist, instead they have bindings. If you set a binding to something like blah = 42
, you do not have a bit of memory that blah is that contains 42, rather there is just ‘42’ (in this case in the constants table), and if you do blorp = blah
then same thing, blah does not exist either, there is still only a single 42. If you do something like vweep = blorp * 2
then vweep
is basically just 42 * 2
, you may not even consider it already calculated since it might not yet be, it is just a binding to ‘something’, however if you pass it to a function, like zwhoo(vweep)
then the 42 * 2
is calculated to 84
on the stack and passed to the function. If it is passed multiple times to multiple areas, like after the above call you then call twoob(vweep, vweep)
, the response of 84
is not recalculated but just held on the stack and that same bit is passed to the function, only calculated once. (Internally the BEAM does greedy calculate things, but not all functional languages do, like haskell does not). What this means is extra memory is not used, not even pointers, so if you destructure a map like %{vwoop: v} = %{vwoop: 42}
then the 42 in-place is used everywhere as needed, it is not necessarily copied into new memory, literally used in-place (internally of course optimizations may change this). But you can see how this style require immutability, but allows for a lot of benefits.
I’m using shadowing in the normal Pascal/C sense, the old binding still ‘exists’, but any later uses of the new name reference the new binding.
Indeed yep.
Not assigned, rather it just gets bound to the operation, hence why it is called a binding, not an assignment as there may be no memory actually being allocated anywhere, even on the stack, for this. Unsure what you mean about the Enum.map
getting automatically bound, the only automatic binding really is function arguments in a function head…
Nothing changes, ever, remember immutability, once something is set it never changes. It returns a new one (internally optimized of course).
In all cases a blah |> vwoop(dreep, bloop)
always becomes vwoop(blah, dreep, bloop)
. The pipe just takes the thing before it, no matter what it is, and just puts it in the first argument position of the function, it is a very simple transformation macro. In essence this:
42
|> blah(16)
|> blorp()
|> vreep("bloop")
Is this:
vreep(blorp(blah(42, 16)), "bloop")
Just much more readable. The pipe operator is popular in a huge amount of languages, mostly functional languages but it has been coming in mainstream languages for a while now too.