【问题标题】:Why is determining if a function is pure difficult?为什么要确定一个函数是否纯粹是困难的?
【发布时间】:2009-10-22 18:59:41
【问题描述】:

我昨天参加了 StackOverflow 开发日大会,其中一位演讲者正在谈论 Python。他展示了一个 Memoize 函数,我问是否有任何方法可以防止它被用于非纯函数。他说不,这基本上是不可能的,如果有人能想出办法做到这一点,那将是一篇很棒的博士论文。

这让我很困惑,因为编译器/解释器似乎并不难递归解决。在伪代码中:

function isPure(functionMetadata): boolean;
begin
   result = true;
   for each variable in functionMetadata.variablesModified
      result = result and variable.isLocalToThisFunction;
   for each dependency in functionMetadata.functionsCalled
      result = result and isPure(dependency);
end;

这是基本的想法。显然,您需要某种检查来防止对相互依赖的函数进行无限递归,但这并不难设置。

采用函数指针的高阶函数可能有问题,因为它们无法静态验证,但我最初的问题假定编译器有某种语言约束来指定只能将纯函数指针传递给某个参数。如果存在的话,可以用来满足条件。

显然这在编译语言中比解释语言更容易,因为所有这些数字运算都将在程序执行之前完成,因此不会减慢任何速度,但我真的没有看到任何基本问题会使其无法评估。

在这方面有更多知识的人知道我缺少什么吗?

【问题讨论】:

  • 任何变量访问都必须是本地的,而不仅仅是修改过的。结果取决于全局当前值的函数,即使它不修改该全局,显然也不是纯函数。
  • 显而易见的问题:日志记录会影响纯度吗?相反,我想说的是,实际上修改一个全局值(如果这不能抛出),不会影响你的方法的结果,因此即使它显然不是纯的也允许记忆!

标签: algorithm functional-programming static-analysis


【解决方案1】:

您还需要注释每个系统调用,每个 FFI,...

此外,最小的“泄漏”往往会泄漏到整个代码库中。

这不是一个理论上难以解决的问题,但在实践中很难做到整个系统不会脆弱。

顺便说一句,我认为这不是一篇好的博士论文。 Haskell 实际上已经有了这个(一个版本),带有 IO monad。

而且我相信很多人会继续关注这个“实践”。 (疯狂猜测)20 年后我们可能会有这个。

【讨论】:

  • 计算机 AI 虽然只有 4-6 年的时间,但它肯定能够为我们解决这个问题:)
  • 在 Haskell 中,每个函数都是纯函数。 IO 不会改变这一点。好吧,也许除了那些unsafePerformIO.
  • 注解所有系统类是 .NET 正在为他们的代码契约做的。如有疑问,它会假定该方法不是纯方法。
  • Jared:在过去 30 年左右的时间里,计算机 AI 不是“只有 4-6 年”吗? :P
  • @Mason - 我认为 Jared 是在开玩笑。
【解决方案2】:

在 Python 中特别难。由于anObject.aFunc 可以在运行时任意更改,因此您无法在编译时确定anObject.aFunc() 将调用哪个函数,甚至根本无法确定它是否是一个函数。

【讨论】:

  • 哎哟!好吧,我知道这会让事情变得更加困难。
  • 还有更多 - eval、setattr()、gettattr() 做一些奇怪的事情等。像这样的特性使得语言难以静态分析。
【解决方案3】:

除了这里的其他出色答案:您的伪代码仅查看函数是否修改变量。但这并不是“纯粹”的真正含义。 “纯”通常意味着更接近“参照透明”的东西。换句话说,输出完全依赖于输入。因此,像读取当前时间并将其作为结果中的一个因素(或从输入读取,或读取机器状态,或......)这样简单的事情会使函数在不修改任何变量的情况下成为非纯函数。

此外,您可以编写一个修改变量的“纯”函数。

【讨论】:

  • +1 没有副作用并不意味着只使用不可变的值。
【解决方案4】:

当我读到你的问题时,这是我脑海中浮现的第一件事。

类层次结构

确定一个变量是否被修改包括挖掘变量上调用的每一个方法以确定它是否正在发生变化。这对于使用非虚拟方法的密封类型来说有点直截了当。

但是考虑虚拟方法。您必须找到每个派生类型并验证该方法的每个覆盖都不会改变状态。在任何允许动态代码生成或只是动态的语言/框架中根本不可能确定这一点(如果可能,那将非常困难)。原因是派生类型的集合不是固定的,因为可以在运行时生成一个新的。

以 C# 为例。没有什么能阻止我在运行时生成一个派生类,它会覆盖该虚拟方法并修改状态。静态验证将无法检测到这种类型的修改,因此无法验证该方法是否纯正。

【讨论】:

  • 哦,好点。多态性肯定会使事情复杂化。尽管可以通过在基本虚函数上放置“纯约束”来解决这个问题。
【解决方案5】:

我认为主要的问题是高效地完成它。

D 语言有纯函数,但你必须自己指定它们,所以编译器会知道检查它们。我认为如果您手动指定它们,那么它会更容易做到。

【讨论】:

    【解决方案6】:

    一般来说,判断一个给定的函数是否是纯函数可以简化为判断任何给定的程序是否会停止 - 众所周知,停止问题是一种无法有效解决的问题。

    【讨论】:

    • 我知道停机问题,但你为什么说它是等价的?
    • 假设我编写了一个程序 P 来模拟某些输入函数 F 的执行(即 P 是一个解释器)。假设 P 被写成它在完成 F 的评估后立即停止,并且在执行 F 的任何不纯步骤后立即停止(输出指示它停止的原因)。因为某些 F 可能具有 f a = f a 的形式 - 具有一个参数的函数的 Haskell 语法,该函数简单地用相同的参数无限递归地调用自身 - 有一些 F 执行 F 的 P 不会停止。因此,OP 的问题可以简化为停机问题。
    • 请注意,在 Haskell 中,如果我们摆脱了允许某些不纯代码包含在纯代码中的语言漏洞,编译器可以很容易地确定哪些代码肯定是纯的,哪些代码不确定是纯的通过查看类型。 Haskell 表示类型级别的杂质。
    • 一般来说,计算中的纯函数不排除非终止(或异常终止)函数。 Haskell 实际上将非终止定义为一种特殊类型的返回值(“底部”),它有效地毒化任何试图检查它的操作。
    【解决方案7】:

    请注意,复杂性也取决于语言。对于更动态的语言,可以随时重新定义任何内容。例如,在 Tcl 中

    proc myproc {a b} {
        if { $a > $b } {
            return $a
        } else {
            return $b
        }
    }
    

    其中的每一部分都可以随时修改。例如:

    • 可以重写“if”命令以使用和更新全局变量
    • “return”命令,按照同样的思路,可以做同样的事情
    • 可能是 if 命令的执行跟踪,当使用“if”时,返回命令会根据 if 命令的输入重新定义

    诚然,Tcl 是一个极端的例子。最有活力的语言之一。话虽如此,它突出了一个问题,即即使输入了函数也很难确定函数的纯度。

    【讨论】:

      猜你喜欢
      • 2015-09-01
      • 2012-08-04
      • 1970-01-01
      • 2012-08-08
      • 2013-03-06
      • 1970-01-01
      • 1970-01-01
      • 2016-12-30
      • 1970-01-01
      相关资源
      最近更新 更多