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

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

问题描述

我最近阅读了很多关于函数式编程的内容,我可以理解其中的大部分内容,但是我无法理解的一件事是无状态编码.在我看来,通过移除可变状态来简化编程就像通过移除仪表板来简化"汽车:成品可能更简单,但祝你好运,让它与最终用户互动.

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.

几乎我能想到的每个用户应用程序都将状态作为核心概念.如果您编写文档(或 SO 帖子),状态会随着每个新输入而变化.或者,如果您玩电子游戏,则有大量状态变量,从所有角色的位置开始,他们往往会不断移动.如果不跟踪值的变化,你怎么可能做任何有用的事情?

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?

每次我发现讨论这个问题的东西时,它都是用真正技术性的功能性语言编写的,假设我没有深厚的 FP 背景.有没有人知道如何向对命令式编码有良好、扎实理解但又是功能方面的完全n00b 的人解释这一点?

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?

到目前为止,一堆回复似乎试图让我相信不可变值的优点.我明白那部分.这是完全有道理的.我不明白的是如何在没有可变变量的情况下跟踪必须更改并不断更改的值.

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?

如果您有兴趣,这里是一系列介绍 Erlang 游戏编程的文章.

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

您可能不会喜欢这个答案,但除非您使用它,否则您不会获得 函数式程序.我可以发布代码示例并说在这里,你不看到"——但如果你不理解语法和基本原理,那么你的眼睛就会呆滞.从您的角度来看,我似乎在做与命令式语言相同的事情,但只是设置了各种边界来有目的地使编程变得更加困难.我的观点是,您只是在体验 Blub 悖论.

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.

起初我持怀疑态度,但几年前我跳上了函数式编程的火车并爱上了它.函数式编程的诀窍是能够识别模式、特定的变量赋值,并将命令式状态移动到堆栈中.例如,for 循环变为递归:

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)

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

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);
        }
    }
}

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

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.

举一个更重要的例子,我还写了这个完全无状态的 SQL 解析器(或者至少我的代码是无状态的,我不知道底层词法库是否是无状态的).

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天全站免登陆