【问题标题】:Is Returning A Random Value From Function A Side Effect?从函数返回随机值是副作用吗?
【发布时间】:2011-05-22 10:46:20
【问题描述】:

我正在编写一些 F# 代码,并且正在编写一个函数来从一组字符串中返回一个随机字符串。假设我有这样的事情:

open System

let a = [|"a";"b";"c";"d"|]

let rstring (arr:string[]) =
   let r = new Random()
   arr.[r.Next(0,3)]

let resultstring = rstring a;;

我的问题是:我对函数式编程概念的理解是,如果给定函数每次都具有相同的输入,则它应该始终返回相同的输出。那么在这种特殊情况下,每次“副作用”都会返回不同的字符串?我只是好奇。

如果这是一个重复的问题,只需将我指向原始问题,我将关闭它。我不确定我会使用什么搜索字符串来查找与此相关的任何问题。


编辑:感谢大家提供的所有信息。似乎我将引用透明性和没有副作用的概念混为一谈了。所以,感谢大家让我明白其中的不同之处,并感谢您的回答。

【问题讨论】:

  • 总是为相同的参数返回相同的结果实际上意味着纯粹,而不是无副作用(副作用的定义已经发布)。
  • 当您开始挑剔时,“副作用”在软件中并不是一个有趣的概念。引用透明度和/或纯度是有趣/有用的概念,随机/非确定函数不是引用透明的,这才是最重要的。因此,从某种意义上说,OP 提出了错误的问题,或者至少不是一个有趣/有用的问题。标题提出的问题挑剔了定义。对软件来说重要的是,如果你的函数为相同的输入返回不同的值,那么它会给你带来一整类错误。
  • @delnan - 根据定义,纯函数要求函数没有副作用。仅仅因为一个函数总是返回相同的结果并不意味着它是纯粹的。

标签: f# functional-programming


【解决方案1】:

是的。

函数可以从同一输入返回不同值的唯一方法是通过副作用。其他人可能会声称不这样做,但他们错了。

(您可以声称“读取系统时间”(显然基于效果)不是效果。根据这个定义,它不是副作用。但是这个定义没有用,因为人们的唯一原因“关心”副作用是因为它们会影响引用透明性。换句话说,引用透明性是唯一重要的事情,而这个函数显然不是引用透明的。)

【讨论】:

  • 真的 - 一个随机数发生器连接到监测大气噪声的硬件怎么样。为什么它必然需要改变程序状态?
  • 大气状态现在是程序状态的一部分。在一个完全静止的空气室中,状态可能没有变化,但现在房间中的空气是您的计算机程序正在模拟的计算的一部分。
  • 为了扩展 Brian 的观点,在这种情况下程序的状态正在改变,即使你没有明确地改变它。 (这样想对我有帮助,认为它可能对其他人有帮助)
【解决方案2】:

“副作用”是指函数改变了某些状态,而不是返回值不同。在您的情况下,该函数同时执行这两种操作 - 它正在更改 PRNG 的状态并返回不同的值。

编辑:

我还要补充一点,如果一个函数总是为给定的输入返回相同的值,它被称为幂等。 “副作用”还包括从外部状态读取的函数。例如:

int global = 0;
int function()
{
  return global;
}

有副作用。

【讨论】:

  • "如果一个函数总是为给定的输入返回相同的值,它被称为幂等" -- 我讨厌成为那个女孩,但是您所拥有的是 pure 的定义,而不是 idempotent,它指的是满足所有 x 的属性 f(f(x)) == f(x) 的函数 flet abs x = if x < 0 then -x else x 是纯幂等的,因为 abs(abs(x)) == abs(x)。函数let succ x = x + 1 是纯函数但不是幂等的,因为succ(succ(x)) != succ(x)
  • @Juliet - 从数学上讲这是正确的。在计算机科学中,我听说过“幂等”一词既表示f(f(x))==f(x),又表示“对于给定的输入总是返回相同的值”。也许这个词被错误地使用了。此外,pure 具有更严格的含义——一个函数总是可以为给定的输入返回相同的值,但也会产生副作用,使其不纯。
  • 对于 C 或 C++,从 global 读取没有副作用,除非 globalvolatile 类型。
【解决方案3】:

函数可以是不确定的。这不是副作用。一个副作用是,除了返回一个值之外,它还以某种方式改变了程序的状态。我想说副作用和非确定性都是函数式编程中的函数不同于数学函数的方式。

【讨论】:

  • 嗯,它确实改变了 RNG 的状态。尽管仅此副作用不会影响纯度(在结果中包含非纯随机数会影响)。
  • @delnan -- 不要将示例与问题混淆。问题是不确定性是否等同于副作用。该示例既不确定又具有副作用的事实并不意味着不确定性是副作用。
  • 这里有两个问题。标题问题:“从函数返回随机值是否有副作用?”有“是”的具体答案。更普遍的问题:“不确定性是副作用吗?”正如你所指出的,有不同的答案。虽然我想不出没有副作用的非确定性函数的例子。
  • @Niki - 这仅适用于这个特定的实现。我可以很容易地构想出一个不影响程序状态的真正随机数生成器。并不是说它对普通人群特别有用。
  • 是的,我指的是 PRNG。虽然我很确定“副作用”包括读取有状态的内容(与修改相反),但读取 RNG 的行为将被视为副作用。
【解决方案4】:

忽略随机生成器的实现细节(实际上需要副作用,理论上不需要),那么这个函数是无副作用的,但不是纯粹的。

基本上把函数调用想象成一个预言机,它完全不基于任何内容返回随机数。有证据表明你不能这样做,但暂时想象一下吧。

如果您将 PRNG 函数视为每次请求时都会给您一个随机数的预言机,那么它是无副作用且不纯的,并且该函数会继承它。

不违反引用透明性的副作用类型有时被称为“软副作用”,因为它不会改变程序的行为(注意:在你告诉我读取时间会改变生成的数字的行为, 函数调用的规范说函数返回一个随机数. 读取时钟是使该规范工作的实现细节. 更改时钟(除非在极端退化的情况下)不会改变该函数返回一个伪随机数的事实,因为规范没有详细说明该数字是什么。

软副作用的典型例子是日志记录,因为程序通常不会检查它们的日志并根据它改变行为。

如果您想以纯功能的方式执行此操作,则必须实现一个状态单子。

http://fsharpcode.blogspot.com/2008/12/f-state-monad-type-state-state-state-of.html

基本上就是这样,让您声明一个随机数生成器和 'a 一个 int,然后您的控制流会将杂质隔离到单个函数并在排序上创建一个硬顺序。

在 F# 中这样做确实没有意义,因为具有不纯函数并不会真正导致问题(另一方面,具有副作用的函数会)。

当然,monad 是不纯的,但它很好地隔离了杂质。

【讨论】:

    【解决方案5】:

    我不相信你的例子会被视为副作用。

    http://en.wikipedia.org/wiki/Side_effect_(computer_science)

    【讨论】:

    • Random() 引起副作用,所以 rstring 引起副作用。
    • @Niki - 在这种特定情况下可能是正确的,但通常简单地返回一个不确定的值本身并不是副作用。
    • @Niki Random() 的副作用是什么?
    • @Mike Cheel - Random() 由有状态的系统时间播种,r.Next(0,3) 修改了 r 的内部结构。
    • @Niki 我认为它需要修改某些东西的状态或者是可观察的。读取系统时钟不会修改时间,并且 r 在函数内部定义,因此不可观察(私有)。根据 wiki:在计算机科学中,如果一个函数或表达式除了产生一个值之外,它还修改了某些状态或与调用函数或外部世界有可观察的交互,则称其具有副作用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-19
    • 2016-11-03
    • 2012-03-12
    • 2014-04-19
    • 1970-01-01
    • 2019-12-04
    • 2012-04-25
    相关资源
    最近更新 更多