函数式编程中不可变数据的问题

时间:2016-01-16 15:15:04

标签: multithreading haskell clojure functional-programming erlang

我是函数式编程的新手。我所理解的是函数式编程是使用纯函数编写代码而不改变数据的价值。

当我们需要更新变量时,我们不是改变变量的值而是在函数式编程中创建新的变量。

假设我们有一个变量x,它表示程序发出的HTTP请求总数。如果我们有两个线程,那么每当任何线程发出HTTP请求时,我都希望线程递增x。如果两个线程都为变量x制作了不同的副本,那么它们如何同步x的值。例如:如果线程1发出10个HTTP请求,而线程2发出11个HTTP请求,那么它们将分别打印10和11但是如何打印21。

6 个答案:

答案 0 :(得分:8)

我可以为recyclerView.post(new Runnable() { public void run() { posListRecyclerView.setAdapter(new postListRecyclerViewAdapter(PostListActivity.this, postDataList));}}); 案例提供答案。在clojure中,如果您需要协调对共享状态的访问,那么语言中的构造将用于处理这些情况。

在这种情况下,您可以使用clojure来保存该值。对atom所做的更改是原子的,并且将通过clojure的STM乐观地进行。原子是clojure的参考类型之一。原子本质上是对一个值的引用,它可以通过原子的变异函数以受控的方式随时间变化。

有关原子和其他引用类型的更多信息,请参见clojure docs

答案 1 :(得分:1)

我将解决Haskell部分。 MVar是线程的通信机制之一。这是Simon Marlow的书(该程序不言自明)中的一个例子:

main = do
  m <- newEmptyMVar
  forkIO $ do putMVar m 'x'; putMVar m 'y'
  r <- takeMVar m
  print r
  r <- takeMVar m
  print r

上述程序的输出将为:

'x'
'y'

您可以在上面的示例中看到如何在线程之间共享变量MVar中的m值。您可以在this book中了解有关这些技术的更多信息。

答案 2 :(得分:0)

我还将讨论Haskell部分。

首先,我想澄清一些事情:

  

我们不是改变变量的值而是创建新的变量   我们需要更新变量时的函数式编程。

那不太准确。我们创造了新的变量&#34;在我们需要它们时,不是在我们需要改变现有的时候。当我们做你所描述的事情时,我们甚至不会在突变方面思考;我们可能只是认为我们创造的新价值与我们的价值相似。

你用线程描述的内容有点不同。你实际上在寻找副作用(增加一个计数器)。 Haskell是纯粹的,不会让你在不明确的情况下抛出副作用。因此,在这种情况下,您将需要求助于引用类型/可变单元格。最简单的一个被称为IORef,它在这个意义上非常像一个变量;您可以分配值,读取当前值等。

所以,正如你所看到的,当你正在寻找这些东西时,你真的只有一个柜台副本。

以上是我答案的精髓,但您已经具体询问过线程,所以我也会对此作出回应。
IORef实际上并不是线程安全的。所以建议MVar。它们不像常规变量,但它们很接近,它们可以优雅地完成工作。一般而言,松散地说:它们抽象变量和锁定。我想你可能会发现TVar更容易。它们的行为类似于IORef /变量,只是与两者不同,它们是线程安全的;你可以将它们的操作组合成一个操作,并且对它们进行的任何操作都是以原子方式完成的(STM)。

顺便说一下,你可能会找到完全避免状态的方法,这是非常鼓励的。例如。你可以让两个线程执行一个异步递归函数,该函数通过参数记住已经发出了多少请求,然后将它作为返回值。请求总数是所有线程返回的请求总和。这可以避免计数器上的副作用,但它只能在线程完成时产生结果。这非常有限,所以有时你可能会想要这种副作用。

答案 3 :(得分:0)

嗯,我会尝试在持有州时提供更一般的解释,因为我认为这是你真正想知道的。

通常,您可以通过递归完成相同的操作,例如,如果您有以下功能:

somefun ()->
   somefun(0).
somefun (X) ->
  perform_http_request(),
  if(something!=quit)
     somefun(X+1)
end function.

generate_thread(0, Accumulator) ->
      Accumulator;
generate_thread(X, Accumulator) ->
      Y = somefun(),
      NewAccumulator = add_to_accumulator(Y),
      generate_thread(X-1, NewAccumulator).

我只是匆匆输入这个,这是一个非常通用的解释(你将不能直接使用这个代码)但是你认为你可以发现你真的没有这里的可变性......这个函数会当所有线程完成处理时完成,现在你可以根据你选择的语言进行实际的线程同步,不同的语言有不同的处理并发和“线程”的方式..我建议你看一下Erlang如果你是并发的,因为它有一个非常好的并发模型imo。

无论如何,最后你可以将累加器中返回的所有值相加并显示出来,顺便看看foldl和foldr函数。

答案 4 :(得分:0)

我自己不是专家,但我想您可能想念您。

没有保持状态,您将无法创建非常有用的程序。需要陈述一种或另一种方式。 FP的目标不是避免状态,是要控制状态的使用

以这种方式查看它,您的状态应该与数据库条目一样孤立并且安全。 如果您像对待数据库一样对待状态,我认为您会没事的

这意味着

  • 您将不会像(inc count)这样的登录名。您宁愿拥有函数increment-count!,该函数将安全地更新计数。注意!,这意味着副作用。
  • 您将没有依赖于副作用的代码。相反,您将依赖于期望从其参数中获得一切的功能。除非他们绝对必须依靠国家。就像更新计数一样,这无疑是对状态的呼唤。
  • 您的第一选择应该是避免状态。当不可能将其传递给函数时,您将创建正确更新的状态。
  • 处理状态,就像对待外部API一样。您必须使用某种协议来远程访问。

我希望这是有道理的。

答案 5 :(得分:0)

我将地址Erlang部分。即使在Erlang中,同步也没有神奇之处,因为某人必须处理同步。它只是Erlang,具有不变性(也就是没有变量)的优点,有助于防止并发编程中常见的同步错误。像gen_server这样的Erlang / OTP已经具有管理状态的基础结构。实际上,gen_server是单线程的,它接收到的任何消息都在邮箱中排队。这是有关Erlang邮件并发性的链接。 How Erlang processes access mailbox concurrently

在原始发布情况下,要挂起http请求计数器,可以使用单个gen_server OTP(Erlang)。您会惊讶于它可以处理多少吞吐量。如果单个gen_server吞吐量确实不足,则可以使用分层的gen_server汇总计数。 Erlang / OTP带有一组运行时API,用于实时测量性能。