How can you do anything useful without mutable state?

后端 未结 18 1652
栀梦
栀梦 2020-11-28 17:12

I\'ve been reading a lot of stuff about functional programming lately, and I can understand most of it, but the one thing I just can\'t wrap my head around is stateless codi

相关标签:
18条回答
  • 2020-11-28 17:53

    For highly interactive applications such as games, Functional Reactive Programming is your friend: if you can formulate the properties of your game's world as time-varying values (and/or event streams), you are ready! These formulae will be sometimes even more natural and intent-revealing than mutating a state, e.g. for a moving ball, you can directly use the well-known law x = v * t. And what's better, the game's rules written such way compose better than object-oriented abstractions. For example, in this case, the ball's speed can be also a time-varying value, which depends on the event stream consisting of the ball's collisions. For more concrete design considerations, see Making Games in Elm.

    0 讨论(0)
  • 2020-11-28 17:55

    I think there's a slight misunderstanding. Pure functional programs have state. The difference is how that state is modeled. In pure functional programming, state is manipulated by functions that take some state and return the next state. Sequencing through states is then achieved by passing the state through a sequence of pure functions.

    Even global mutable state can be modeled this way. In Haskell, for example, a program is a function from a World to a World. That is, you pass in the entire universe, and the program returns a new universe. In practise, though, you only need to pass in the parts of the universe in which your program is actually interested. And programs actually return a sequence of actions that serve as instructions for the operating environment in which the program runs.

    You wanted to see this explained in terms of imperative programming. OK, let's look at some really simple imperative programming in a functional language.

    Consider this code:

    int x = 1;
    int y = x + 1;
    x = x + y;
    return x;
    

    Pretty bog-standard imperative code. Doesn't do anything interesting, but that's OK for illustration. I think you will agree that there's state involved here. The value of the x variable changes over time. Now, let's change the notation slightly by inventing a new syntax:

    let x = 1 in
    let y = x + 1 in
    let z = x + y in z 
    

    Put parentheses to make it clearer what this means:

    let x = 1 in (let y = x + 1 in (let z = x + y in (z)))
    

    So you see, state is modeled by a sequence of pure expressions that bind the free variables of the following expressions.

    You will find that this pattern can model any kind of state, even IO.

    0 讨论(0)
  • 2020-11-28 17:55

    It is in fact quite easy to have something which looks like mutable state even in languages without mutable state.

    Consider a function with type s -> (a, s). Translating from Haskell syntax, it means a function which takes one parameter of type "s" and returns a pair of values, of types "a" and "s". If s is the type of our state, this function takes one state and returns a new state, and possibly a value (you can always return "unit" aka (), which is sort of equivalent to "void" in C/C++, as the "a" type). If you chain several calls of functions with types like this (getting the state returned from one function and passing it to the next), you have "mutable" state (in fact you are in each function creating a new state and abandoning the old one).

    It might be easier to understand if you imagine the mutable state as the "space" where your program is executing, and then think of the time dimension. At instant t1, the "space" is in a certain condition (say for example some memory location has value 5). At a later instant t2, it is in a different condition (for example that memory location now has value 10). Each of these time "slices" is a state, and it is immutable (you cannot go back in time to change them). So, from this point of view, you went from the full spacetime with a time arrow (your mutable state) to a set of slices of spacetime (several immutable states), and your program is just treating each slice as a value and computing each of them as a function applied to the previous one.

    OK, maybe that was not easier to understand :-)

    It might seem inneficient to explicitly represent the whole program state as a value, which has to be created only to be discarded the next instant (just after a new one is created). For some algorithms it might be natural, but when it is not, there is another trick. Instead of a real state, you can use a fake state which is nothing more than a marker (let's call the type of this fake state State#). This fake state exists from the point of view of the language, and is passed like any other value, but the compiler completely omits it when generating the machine code. It only serves to mark the sequence of execution.

    As an example, suppose the compiler gives us the following functions:

    readRef :: Ref a -> State# -> (a, State#)
    writeRef :: Ref a -> a -> State# -> (a, State#)
    

    Translating from these Haskell-like declarations, readRef receives something which resembles a pointer or a handle to a value of type "a", and the fake state, and returns the value of type "a" pointed to by the first parameter and a new fake state. writeRef is similar, but changes the value pointed to instead.

    If you call readRef and then pass it the fake state returned by writeRef (perhaps with other calls to unrelated functions in the middle; these state values create a "chain" of function calls), it will return the value written. You can call writeRef again with the same pointer/handle and it will write to the same memory location — but, since conceptually it is returning a new (fake) state, the (fake) state is still imutable (a new one has been "created"). The compiler will call the functions in the order it would have to call them if there was a real state variable which had to be computed, but the only state which there is is the full (mutable) state of the real hardware.

    (Those who know Haskell will notice I simplified things a lot and ommited several important details. For those who want to see more details, take a look at Control.Monad.State from the mtl, and at the ST s and IO (aka ST RealWorld) monads.)

    You might wonder why doing it in such a roundabout way (instead of simply having mutable state in the language). The real advantage is that you have reified your program's state. What before was implicit (your program state was global, allowing for things like action at a distance) is now explicit. Functions which do not receive and return the state cannot modify it or be influenced by it; they are "pure". Even better, you can have separate state threads, and with a bit of type magic, they can be used to embed an imperative computation within a pure one, without making it impure (the ST monad in Haskell is the one normally used for this trick; the State# I mentioned above is in fact GHC's State# s, used by its implementation of the ST and IO monads).

    0 讨论(0)
  • 2020-11-28 17:56

    JavaScript provides very clear examples of the different ways of approaching mutable or immutable state\values within its core because the ECMAScript specifications were not able to settle on a universal standard so one must continue to memorize or doublecheck which functions create a new object that they return or modify the original object passed to it. If your entire language is immutable then you know you are always getting a new (copied & possibly modified) result and never have to worry about accidentally modifying the variable before passing it into a function.

    Do you know which returns a new object and which changes the original of the following examples?

    Array.prototype.push()
    String.prototype.slice()
    Array.prototype.splice()
    String.prototype.trim()
    
    0 讨论(0)
  • 2020-11-28 17:59

    This is very simple. You can use as many variables as you want in functional programming...but only if they're local variables (contained inside functions). So just wrap your code in functions, pass values back and forth among those functions (as passed parameters and returned values)...and that's all there is to it!

    Here's an example:

    function ReadDataFromKeyboard() {
        $input_values = $_POST[];
        return $input_values;
    }
    function ProcessInformation($input_values) {
        if ($input_values['a'] > 10)
            return ($input_values['a'] + $input_values['b'] + 3);
        else if ($input_values['a'] > 5)
            return ($input_values['b'] * 3);
        else
            return ($input_values['b'] - $input_values['a'] - 7);
    }
    function DisplayToPage($data) {
        print "Based your input, the answer is: ";
        print $data;
        print "\n";
    }
    
    /* begin: */
    DisplayToPage (
        ProcessInformation (
            GetDataFromKeyboard()
        )
    );
    
    0 讨论(0)
  • 2020-11-28 18:00

    Functional programming avoids state and emphasizes functionality. There's never any such thing as no state, though the state might actually be something that's immutable or baked into the architecture of what you're working with. Consider the difference between a static web server that just loads up files off the filesystem versus a program that implements a Rubik's cube. The former is going to be implemented in terms of functions designed to turn a request into a file path request into a response from the contents of that file. Virtually no state is needed beyond a tiny bit of configuration (the filesystem 'state' is really outside the scope of the program. The program works the same way regardless of what state the files are in). In the latter though, you need to model the cube and your program implementation of how operations on that cube change its state.

    0 讨论(0)
提交回复
热议问题