什么是数学函数和编程函数的区别?

31

在数学和编程中,函数有什么区别?


11
需要翻译的内容:It should be noted that not all functions in programming are the same. In a pure functional programming language like Haskell, functions are closer to functions in mathematics. In imperative languages, they can be quite different.并非所有编程语言中的函数都相同。在纯函数式编程语言Haskell中,函数更接近于数学中的函数。而在命令式编程语言中,它们可能非常不同。 - Jacob
你有没有想要使用的特定编程语言? - Brian
是的,我在谈论C语言函数。 - Alon Gubkin
6个回答

29
在函数式编程中,有引用透明性的概念,这意味着您可以将函数替换为其值而不会改变程序。在数学中,这也是正确的,但在命令式语言中,并非总是如此

数学函数的定义是:将来自一个集合(A)的元素映射到另一个集合(B)的关系,将第一个集合的每个元素与另一个集合中的一个元素对应。 在 C(以及其他编程语言)中,这也是真实的,您有输入集和输出集(几乎总是只有一个)。

主要的区别在于,如果您在数学中调用 f(x),则将始终获得相同的答案,但如果您在 C 中调用 f'(x),则答案可能不相同 - 相同的参数不总是返回相同的输出。非函数式语言中的函数可能不仅取决于您提供给它们的参数,还取决于程序中的其他事物(封闭作用域、全局变量、内置变量、状态(如果使用面向对象)。

在数学和C函数之间的另一个区别是,在数学中,你不能创建一个从非空集合到空集合的函数(在C中,这将是:您不需要始终返回带有函数的内容)。此外,并非所有的函数都是可计算的(我不知道在数学中是否有类似的东西..)。您没有针对无限集合的函数(您具有有限的内存,因此可能输入参数的可能集合必须是有限的),但在数学中,您可以为无限集合(如f: N -> N)以及不可数的集合(如f: R -> R)定义函数(在C中,您具有浮点数,但它们仅表示实数的一个缩小集合,该集合是有限的)。 总结一下
在C中,您并不总是具有引用透明性。您的函数可能不始终对相同的输入参数给出相同的输出。您可以拥有定义为无限输入集的数学函数,但在C函数中,您的输入是有限的。在C函数中,您可以有不返回任何内容的函数,但在数学中,您不能有这样的函数(如果您有一个非空输入集的函数,则必须将每个元素映射到另一个集合中的元素)。

除了函数式和命令式编程范式之外,还有其他编程范式,但我认为我对它们了解不够,无法正确回答。 - Marco
逻辑编程或约束引擎可以看作是更加声明式的,类似于数学,而不像C语言通常是命令式的。函数式编程也可以非常声明式,但其他范式使其更加明显,我个人认为。 - jk.
3
换言之,C函数不要求是“纯净”的(如http://en.wikipedia.org/wiki/Pure_function)。 - user395760
没错,delnan。我不知道它们被称为“纯函数”。 - Marco
我之前对于在数学中证明事物的方法有所误解。实际上,你可以证明一个 C 函数满足一个形式规范,这等同于证明一个数学函数满足某些属性。但是,证明某个事物的复杂度与此无关。我已经编辑了我的帖子,并添加了一些关于输入集合域的内容,并提到了可计算函数。 - Marco
写得很好,说明也很清楚。+1 - Vivin Paliath

6
这取决于领域(我指的不是函数的域,而是研究的领域),可能还取决于语言。
在数学中,对于给定的输入值(垂直线测试,请记住),函数具有将输入映射到仅一个输出的输入。在编程中,这可能不完全相同,这取决于您在“输入”和“函数逻辑”之间划分界限的位置。
例如,让我们想象一个函数 rand(),它读取大气条件以得到真正的随机数。让我们还想象一种调用函数,它以一个整数参数作为乘数。以下是否为函数?
int giveRandAtmosWithMul(int mult)
{
    return mult * rand();
}

在数学意义上,如果您只考虑mult作为问题的唯一输入,则可能不是一个函数,但显然rand()也提供了输入(即使rand()在机器代码中始终具有相同的入口点)。
正如您所看到的,如果没有一些每个人都同意的标准协议,这些差异实际上无法进行客观定义。

3

我认为最重要的区别在于,数学中的函数(以及函数式编程)不能改变状态,而(命令式)编程函数可以。


2
其他答案是正确的 - 这些是两个不同的东西。相反地,我将展示它们是相关的。我将用->表示编程函数,用=>表示数学函数。
假设您有一种支持异常的语言。在这种情况下,您可以将编程函数A -> B视为数学函数A => B + E,其中“B + E”表示类型为B或类型为E的内容。您的语言有两个关键字来从函数返回,“return”和“throw”。
现在,您可以通过编写g(f(x))组合两个函数f:A -> Bg:B -> C。这是一个接受A并返回C的函数。您可以在许多语言中执行此操作,例如Java或Python。在幕后,如果f(x)引发异常,则不会调用g,而是传播该异常 - 想想g(1/0)。语言会处理这个问题,您不需要检查f的结果是否是异常。描述这个数学上的方式是,尽管f:A => B+Eg:B => C+E,但语言将g函数“提升”到B+E => C+E中。g现在是一个可能引发异常的函数,但它将简单地传播它。
换句话说,通过以下方式定义g':B+E => C+E
        /   g(x)   if x ∈ B
g'(x)= |
        \   x      if x ∈ E

有两个函数 f: A => B+Eg': B+E => C+E,你可以在数学意义上安全地组合它们。这就是编程中的 g(f(x)) 所做的事情,但首先必须将 g 提升到 g'
想一想非确定性。如果你有一个非确定性函数 A -> B,那么你可以用数学方式描述它,说这是一个函数 A => Set(B),其中 Set(B) 是可能结果的集合。例如,如果 f(1) 可能给出 1、2 或 3,则在数学上 f(1) = {1,2,3},尽管在编程时,当要求 f(1) 时,你只会得到其中一个数字。现在,你可以通过编写 g(f(x)) 来组合非确定性函数 A->BB->C。结果将是类型为 C 的非确定性。在数学上,组合函数 A => Set(B)B => Set(C) 将给出 A => Set(C)。虽然 g 只取一个值 B,但你必须将其提升为非确定性值 g': Set(B) => Set(C)。例如,g'({1,2}) 是集合 g(1)g(2) 的并集。因此,g(f(x)) 意味着你运行 f,获取所有可能结果的集合,并在每个结果上运行 g。有两层非确定性,但它们被“展平”成一层。
全局状态也是如此。如果将每个全局变量作为函数的参数和结果,那么你可以认为没有全局变量,每个函数都带有所有全局状态,并且语言在组合时必须交出状态。读取和可能写入状态 S 的函数 A -> B 是一个函数 (A,S) => (B,S),也可以写成 A => (S => (B,S))。正式地写这个更复杂,但是它是相同的模式。
输入/输出也是如此,我在另一个SO答案中描述了这一点。
允许组合“具有效果”的函数的“魔法”是:
  • 一种使类型具有效应的方法。例如,将B转换为B+ESet(B)。我将类型X的具有效应版本表示为F(X)
  • 一种提升函数B -> F(C)F(B) -> F(C)的方法。它允许组合函数A -> F(B)B -> F(C)
  • 关键字return必须将普通值A转换为A+E,或单例Set(A)。因此,它的类型必须是X -> F(X)

由这三个部分组成的结构称为单子。单子允许描述那些副作用。单子可能还具有一些特定的函数,例如异常单子具有throw,非确定性单子具有fork,状态单子具有get/put,IO单子具有read/write等。

道理就是:如果你把像随机、异常、非确定性、输入/输出之类的特殊效应看作是函数结果的一部分,那么每个函数都是参照透明的,而命令式编程中的函数实际上是数学函数,但其结果类型非常奇怪,也描述了特殊效应。这是纯函数语言如Haskell所采取的方法。


1

在数学中,函数不会抛出异常。 :)

在计算机科学中,函数是一段代码,它接受输入,执行某些操作,并可能返回输出,但在此之间它可以执行许多其他操作。它可以获取网页、发送电子邮件、播放视频等等。

在数学中,函数是非常具体且独特的东西。函数通常被描述为一个“机器”,它接受输入并产生输出。虽然计算机科学函数确实接受输入并产生输出,但它们不必像数学所要求的那样具有“相同的输入始终产生相同的输出”的精确性(例如,bool IsMyApplicationRunningInFullScreen() 在没有任何输入的情况下返回各种值)。


1
数学函数是声明性的,即它们总是具有“是什么”的描述,而计算机科学中的函数是命令式的,即它们具有“如何”描述。
参考:《计算机程序的构造和解释》。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接