4 ms·
An unassigned variable in C is not "an unknown", though it is (as an adjective) unknown. It's uninitialized and thus equal to some value though it must be initi
by tel 12y ago
An unassigned variable in C is not "an unknown", though it is (as an adjective) unknown. It's uninitialized and thus equal to some value though it must be initialized first in order to be useful since only then do we know how to predict its behavior.
To break down the difference, consider the idea of names versus the idea of "boxes" or "slots". In some languages those two concepts are undistinguishable because names always refer to locations in memory. In "functional"[1] languages they are separate.
As an example, consider a function in a hypothetical language
def foo (y, x):
let z = get(x)
assign(x, y*y+z)
The (side) effect of this function is merely to update the value stored in the slot named `x` using a function of the value named `y`. To do so, we read from the slot `x`, bind a local name `z` with that value, and then assign back to the slot named `x`. We would call it in a block like this
let box = newBox(1)
let inp = 3
foo(box, inp)
let newVal = get(box)
print(newVal)
and it would print `7` (3*3+1).
I'm being really explicit about the idea of things being "named" by variables instead of just "being" variables. I'm also being really explicit about the notion of how boxes undergo mutation. The reason I do this is to isolate this "naming" property and show how simple it is. Variables-as-unknowns are merely names of values given to them in some context. Usually this is either a function abstraction or a let
let x = 3
# now, here, the name `x` refers to a value `3`
def foo(x):
# now, here, the name `x` refers to some value that
# will later be passed to `foo`
If we need mutation we have to introduce the box idea noted earlier. Boxes with names behave quite similarly to "memory address variables" like what you have in C.
[1] For this given definition of "functional"
- p1esk 12y agoI don't know, something must be wrong with my head, because I'm not any closer to understanding the point of FP. I really do want to understand it though. Your hypothetical language - is it functional? Because if it is, I don't see how it's different from C. If it's not, can you show me the equivalent code in a functional language? This is how I understand your code example: def foo (y, x): let z = get(x) assign(x, y*y+z) Jump to address 'foo', passing a value stored in register y, and an address stored in register x. Load value at address(x) to register z, then manipulate values of y and z, and store the result at address(x). So far so good. let box = newBox(1) let inp = 3 foo(box, inp) let newVal = get(box) print(newVal) Allocate memory for an int, put 1 in there, and store a pointer at register 'box'. Next, put 3 into register 'inp', and jump to 'foo', passing values at 'box' and 'inp' as x and y (registers don't change). 'Foo' updates the value at the address which we refer to as 'box' or 'x', and we load this value to register 'newVal', and print it. The way I see it, if you are using a concept of a 'slot' or a 'box' in your code, you have to use some name (label) to refer to the address of this slot. This label would be the name of a pointer in C, or the name of a variable in Python. If you are using a variable which stores a value directly, the name of such variable points to a register. How can you separate a name from what it points to? What would it mean, and most importantly, why would you want to do that?
- kian 12y agoLet me see if I can clarify. Imagine a (rather useless) program that prints a number. x = 5 x+=2 print x print x this prints the number 7 2 times in the 'context' definition of equality, which we'll denote using let * = ..., each of those assignments generates a context, which I'll show using indentation. let x = 5 let x = x + 2 print x print x These two programs do the same thing. However, using names as mathematical variables instead of 'boxes', there is another program we can create using the same 'sequence' of expression tokens (thus giving us greater expressive power). let x = 5 let x = x + 2 print x print x this program prints 7, and then it prints 5. In other words, we are able to access previous values of our 'variable' x if we want to, whereas this is not the default behavior of a system with mutable variables. This has an added benefit, not really shown in the previous example, of providing zones of protection from interference from the rest of the program. For example, I can always be assured that within the first context, x will always mean 5, and within the nested context, that x will always mean 7. No function or outside line of code can impact the value that x takes on. This really shines when programming in a team - this gives you assurance that to change the output of your section of the code, another teammate will have to directly alter your code. This localizes the source of future bugs in this code to always be in this section of the code, drastically simplifying the debugging process. To make these contexts space efficient, modern functional programming compilers analyze when a program no longer needs access to previous value (because they will never again be used in the future), and marks it as 'garbage' to be collected later. To try and rephrase it one more time - A mutable variable is an actual location in memory. Anyone sharing access to this location in memory in effect has a communication channel open to all of the other linking bits of code. This can be a powerfully simplifying abstraction, in some cases, which is why functional languages generally still provide it by many means, including (in Haskell) the State and IO monads. However, as a default model of resource usage, mutable variables are a poor choice. Generally speaking, you don't really want all parts of the code to be talking to one another. As programmers, whether using procedural, object-oriented, or functional programming, minimizing this kind of communication can help minimize the impacts of future changes. This is why object-oriented languages have private methods and variables, and why there is a trend towards object composition over object inheritance to define more complex behaviors, because both decouple the resulting subsets of code and make them more robust to external forces. Mathematical variables simplify our attempts to achieve this by cutting off the default route for breaking these hierarchical assumptions - shared communication.