你怎么能做任何有用的没有可变状态? [英] How can you do anything useful without mutable state?

查看:71
本文介绍了你怎么能做任何有用的没有可变状态?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我一直在阅读很多关于函数式编程的东西,而且我可以理解它的大部分内容,但是我无法用脑袋包裹的一件事就是无状态编码。在我看来,通过消除可变状态来简化编程就像是通过移除仪表板来简化汽车:成品可能更简单,但运气好,可以与最终用户交互。



几乎所有我能想到的用户应用程序都将状态视为核心概念。如果您编写文档(或SO帖子),则状态会随着每个新输入而改变。或者如果你玩视频游戏,那么就会有很多状态变量,从所有角色的位置开始,这些角色会不断移动。如何在不跟踪变化的价值的情况下做任何有用的事情?

每次我找到讨论这个问题的东西时,都会写出真正的技术功能 - 假设我没有的重型FP背景。有没有人知道一种方式来解释这一点,一个对命令式编码有良好而坚实的理解的人,但是在功能方面谁是完整的n00b?编辑:一堆迄今为止的答复似乎试图让我相信不变价值的优势。我得到那部分。它非常有意义。我不明白的是,如何跟踪必须改变的值,并且不断变化,而没有可变变量。

解决方案

< blockquote>

或者如果您玩视频游戏,则有
的状态变量,从
开始,包含所有
字符的位置,这些字符倾向于移动
不断。你怎么可能做
有用的东西,而不需要记录
的变化值?

如果你有兴趣,这里是一系列描述Erlang游戏编程的文章。



你可能不会喜欢这个答案,但是在你使用它之前,你不会得到功能程序。我可以发布代码示例并说这里,你不看 - 但是如果你不明白语法和基本原理,那么你的眼睛就会黯然失色。从你的角度来看,它看起来好像是在做与命令式语言相同的事情,但只是设置各种界限来有目的地使编程变得更加困难。我的观点是,你只是遇到 Blub悖论



我起初很怀疑,但几年前我跳上功能性编程列车并且爱上了它。函数式编程的技巧是能够识别模式,特定的变量赋值,并将命令状态移动到堆栈。例如,一个for循环成为递归:

  //命令式
让printTo x =
for a 1 .. x do
printfn%ia

//递归
让printTo x =
让rec循环a = if a< = x然后printfn%ia;循环(a + 1)
循环1

它不是很漂亮,但我们得到了没有突变的效果相同。当然,只要有可能,我们都喜欢避免循环并将其抽象出来:

  //首选
让printTo x = seq {1 .. x} |> Seq.iter(fun a - > printfn%ia)

Seq.iter方法将通过集合枚举并为每个项目调用匿名函数。非常方便:)

我知道,打印数字并不令人印象深刻。但是,我们可以对游戏使用相同的方法:将所有状态保存在堆栈中,并使用递归调用中的更改创建一个新对象。通过这种方式,每个框架都是游戏的无状态快照,每个框架只需创建一个全新的对象,并随需要更新任何无状态对象的所需更改。伪代码可能是:

$ p $ //命令式版本
pacman = new pacman(0,0)
而真
如果key = UP那么pacman.y ++
elif key = DOWN然后pacman.y--
elif key = LEFT然后pacman.x--
elif键= UP然后pacman.x ++
渲染(pacman)

//功能版本
让rec循环pacman =
渲染(pacman)
让x, y = switch(key)
case LEFT:pacman.x - 1,pacman.y
case RIGHT:pacman.x + 1,pacman.y
case UP:pacman.x,pacman .y - 1
case DOWN:pacman.x,pacman.y + 1
loop(new pacman(x,y))

命令式和功能式版本是相同的,但功能版本明显不使用可变式状态。功能代码保持所有状态都保存在堆栈上 - 这种方法的好处是,如果出现问题,调试很容易,只需要一个堆栈跟踪。



这可以扩展到游戏中的任意数量的对象,因为所有对象(或相关对象的集合)都可以在其自己的线程中呈现。


几乎每个用户应用程序I
都可以考虑将状态作为核心
概念。


在函数式语言中,我们只是用我们想要的改变返回一个新的对象,而不是改变对象的状态。它比听起来更有效率。例如,数据结构很容易表示为不可变的数据结构。例如,Stack非常容易实现:

  using System; 

命名空间ConsoleApplication1
{
static class Stack
{
public static Stack< T> (T hd,Stack T tl){return new Stack T(hd,tl); }
public static Stack< T> Append< T>(Stack< T> x,Stack< T> y)
{
return x == null? y:Cons(x.Head,Append(x.Tail,y)); (x< T> f){if(x!= null){f(x.Head); Iter(x.Tail,f); }}
}

class Stack< T>
{
public readonly T Head;
public readonly Stack< T>尾巴;
public Stack(T hd,Stack< T> tl)
{
this.Head = hd;
this.Tail = tl;



类程序
{
static void Main(string [] args)
{
Stack< int> ; x = Stack.Cons(1,Stack.Cons(2,Stack.Cons(3,Stack.Cons(4,null))));
Stack< int> y = Stack.Cons(5,Stack.Cons(6,Stack.Cons(7,Stack.Cons(8,null))));
Stack< int> z = Stack.Append(x,y);
Stack.Iter(z,a => Console.WriteLine(a));
Console.ReadKey(true);





$ p
$ b

上面的代码构造了两个不可变的列表,将它们附加在一起以创建新列表,并追加结果。在应用程序的任何地方都没有使用可变状态。它看起来有点笨重,但那只是因为C#是一种冗长的语言。以下是F#中的等效程序:

  type'a stack = 
| 'a *'缺点
|的缺点无

让rec追加x y =
匹配x与
|缺点(hd,tl) - >缺点(hd,append tl y)
|无 - > y

让rec iter f =函数
|缺点(hd,tl) - > F(HD); iter f tl
|无 - > ()

let x = Cons(1,Cons(2,Cons(3,Cons(4,Nil))))
let y = Cons(5,Cons (7,Cons(8,Nil))))
let z =追加xy
iter(fun a - > printfn%ia)z

无法创建和操作列表。几乎所有的数据结构都可以很容易地转换为它们的功能等价物。我写了一个这里的页面,它提供了堆栈,队列,左边的堆,黑树,懒惰的名单。没有一段代码包含任何可变状态。为了改变一棵树,我创建了一个全新的节点,并创建了一个我想要的新节点 - 这非常有效,因为我不需要复制树中的每个节点,我可以在新的节点中重新使用旧节点树。



使用更重要的示例,我还写了这个SQL语法分析器是完全无状态的(或者至少我的代码是无状态的,我不知道底层的lexing库是否是无状态的)。



无状态编程和有状态编程一样具有表现力和强大功能,只需要一些练习来训练自己开始无状态地思考。当然,尽可能无状态编程,必要时有状态编程似乎是大多数不纯功能语言的座右铭。当功能性方法不够干净或高效时,回退可变性没有任何坏处。


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 coding. It seems to me that simplifying programming by removing mutable state is like "simplifying" a car by removing the dashboard: the finished product may be simpler, but good luck making it interact with end-users.

Just about every user application I can think of involves state as a core concept. If you write a document (or a SO post), the state changes with every new input. Or if you play a video game, there are tons of state variables, beginning with the positions of all the characters, who tend to move around constantly. How can you possibly do anything useful without keeping track of changing values?

Every time I find something that discusses this issue, it's written in really technical functional-ese that assumes a heavy FP background that I don't have. Does anyone know a way to explain this to someone with a good, solid understanding of imperative coding but who's a complete n00b on the functional side?

EDIT: A bunch of the replies so far seem to be trying to convince me of the advantages of immutable values. I get that part. It makes perfect sense. What I don't understand is how you can keep track of values that have to change, and change constantly, without mutable variables.

解决方案

Or if you play a video game, there are tons of state variables, beginning with the positions of all the characters, who tend to move around constantly. How can you possibly do anything useful without keeping track of changing values?

If you're interested, here's a series of articles which describe game programming with Erlang.

You probably won't like this answer, but you won't get functional program until you use it. I can post code samples and say "Here, don't you see" -- but if you don't understand the syntax and underlying principles, then your eyes just glaze over. From your point of view, it looks as if I'm doing the same thing as an imperative language, but just setting up all kinds of boundaries to purposefully make programming more difficult. My point of view, you're just experiencing the Blub paradox.

I was skeptical at first, but I jumped on the functional programming train a few years ago and fell in love with it. The trick with functional programming is being able to recognize patterns, particular variable assignments, and move the imperative state to the stack. A for-loop, for example, becomes recursion:

// Imperative
let printTo x =
    for a in 1 .. x do
        printfn "%i" a

// Recursive
let printTo x =
    let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
    loop 1

Its not very pretty, but we got the same effect with no mutation. Of course, wherever possible, we like avoid looping altogether and just abstract it away:

// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)

The Seq.iter method will enumerate through the collection and invoke the anonymous function for each item. Very handy :)

I know, printing numbers isn't exactly impressive. However, we can use the same approach with games: hold all state in the stack and create a new object with our changes in the recursive call. In this way, each frame is a stateless snapshot of the game, where each frame simply creates a brand new object with the desired changes of whatever stateless objects needs updating. The pseudocode for this might be:

// imperative version
pacman = new pacman(0, 0)
while true
    if key = UP then pacman.y++
    elif key = DOWN then pacman.y--
    elif key = LEFT then pacman.x--
    elif key = UP then pacman.x++
    render(pacman)

// functional version
let rec loop pacman =
    render(pacman)
    let x, y = switch(key)
        case LEFT: pacman.x - 1, pacman.y
        case RIGHT: pacman.x + 1, pacman.y
        case UP: pacman.x, pacman.y - 1
        case DOWN: pacman.x, pacman.y + 1
    loop(new pacman(x, y))

The imperative and functional versions are identical, but the functional version clearly uses no mutable state. The functional code keeps all state is held on the stack -- the nice thing about this approach is that, if something goes wrong, debugging is easy, all you need is a stack trace.

This scales up to any number of objects in the game, because all objects (or collections of related objects) can be rendered in their own thread.

Just about every user application I can think of involves state as a core concept.

In functional languages, rather than mutating the state of objects, we simply return a new object with the changes we want. Its more efficient than it sounds. Data structures, for example, are very easy to represent as immutable data structures. Stacks, for example, are notoriously easy to implement:

using System;

namespace ConsoleApplication1
{
    static class Stack
    {
        public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
        public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
        {
            return x == null ? y : Cons(x.Head, Append(x.Tail, y));
        }
        public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
    }

    class Stack<T>
    {
        public readonly T Head;
        public readonly Stack<T> Tail;
        public Stack(T hd, Stack<T> tl)
        {
            this.Head = hd;
            this.Tail = tl;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
            Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
            Stack<int> z = Stack.Append(x, y);
            Stack.Iter(z, a => Console.WriteLine(a));
            Console.ReadKey(true);
        }
    }
}

The code above constructs two immutable lists, appends them together to make a new list, and appends the results. No mutable state is used anywhere in the application. It looks a little bulky, but that's only because C# is a verbose language. Here's the equivalent program in F#:

type 'a stack =
    | Cons of 'a * 'a stack
    | Nil

let rec append x y =
    match x with
    | Cons(hd, tl) -> Cons(hd, append tl y)
    | Nil -> y

let rec iter f = function
    | Cons(hd, tl) -> f(hd); iter f tl
    | Nil -> ()

let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
let z = append x y
iter (fun a -> printfn "%i" a) z

No mutable necessary to create and manipulate lists. Nearly all data structures can be easily converted into their functional equivalents. I wrote a page here which provides immutable implementations of stacks, queues, leftist heaps, red-black trees, lazy lists. Not a single snippet of code contains any mutable state. To "mutate" a tree, I create a brand new one with new node I want -- this is very efficient because I don't need to make a copy of every node in the tree, I can reuse the old ones in my new tree.

Using a more significant example, I also wrote this SQL parser which is totally stateless (or at least my code is stateless, I don't know whether the underlying lexing library is stateless).

Stateless programming is just as expressive and powerful as stateful programming, it just requires a little practice to train yourself to start thinking statelessly. Of course, "stateless programming when possible, stateful programming where necessary" seems to be the motto of most impure functional languages. There's no harm in falling back on mutables when the functional approach just isn't as clean or efficient.

这篇关于你怎么能做任何有用的没有可变状态?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆