【问题标题】:Is writing only static methods equivalent to side-effect free programming in C#?仅编写静态方法是否等同于 C# 中的无副作用编程?
【发布时间】:2011-08-01 13:41:56
【问题描述】:

我有两个问题,源于观察到的 C# 静态方法的行为(我可能会误解):

首先: 递归静态方法是否会在某种意义上通过静态方法在幕后实现的方式进行尾调用优化?

第二: 用静态方法编写整个应用程序并且没有超出本地范围的变量是否等同于函数式编程?我想知道,因为我还没有完全理解我一直听到的关于函数式编程的“无副作用”术语。

编辑: 让我提一下,我确实使用并理解为什么以及何时在普通 C# OO 方法中使用静态方法,并且我确实理解尾调用优化不会显式地对递归静态方法进行。也就是说,我理解尾调用优化是一种尝试在每次传递时停止创建新堆栈帧的尝试,并且我在几个点观察到似乎是在其调用方法的框架内执行的静态方法,尽管我可能误解了我的观察结果。

【问题讨论】:

  • 不,在 C# 中仅编写静态方法不是函数式编程。这是对多范式、主要面向对象的语言的严重滥用。
  • @delnan:我并不是建议我这样做,我试图理解“没有副作用”的含义,以及是否可以按照我所说的方式实现。我每天都写很多快乐的 OO C#,我不需要讲课为什么一个全静态的应用程序在 C# 中毫无意义。 C# 只是我理解其他事物的一个参考框架。
  • 如果你真的想深入了解“无副作用”的编程概念,你应该开始尝试像 Haskell 这样的纯函数式编程语言。您当然可以在 C# 中以纯函数式编程,但 Haskell(与 C# 不同)强烈鼓励这种编程风格,而不仅仅是让它成为可能。
  • @Jimmy:如果你有一个静态方法,它接受一个数组作为参数,然后将数组的第一个元素设置为 42,即使你没有涉及任何非局部变量。也没有理由为什么方法应该是静态的才能没有副作用。您当然可以编写纯函数式、无副作用的代码,同时仍然使用 OO 概念。
  • @Chris:在给定的上下文中这怎么无效。吉米所说的都是静态的,没有全局变量。我的方法当然满足这些要求,所以我很确定这是一个完全有效的反例,即静态和全局无必然意味着无副作用的论点。至于地图,我不明白为什么它不能成为列表上的实例方法(例如在 scala 中)。

标签: c# .net static functional-programming


【解决方案1】:

递归静态方法是否会在某种意义上通过静态方法在幕后实现的方式进行尾调用优化?

静态方法与尾递归优化无关。所有rules同样适用于实例和静态方法,但我个人从不依赖JIT优化赶走我的尾巴。此外,C# compiler doesn't emit tail call instruction but sometimes it is performed anyway。简而言之,你永远不知道

F# 编译器支持尾递归优化,并在可能的情况下将递归编译为循环。
在此question 中查看有关 C# 与 F# 行为的更多详细信息。

用静态方法编写整个应用程序并且没有超出本地范围的变量是否等同于函数式编程?

noyes 都是。

从技术上讲,没有什么能阻止您从静态方法(它本身就是一个静态方法!)调用Console.WriteLine,这显然有 副作用。没有什么能阻止你编写一个不改变任何状态的类(带有实例方法)(即实例方法不访问实例字段)。但是从设计的角度来看,这样的方法作为实例方法并没有真正的意义,对吧?

如果您将项目Add 转换为 .NET Framework List<T>(具有副作用),您将修改其状态。
如果你append一个项目到F# list,你会得到另一个列表,并且原来的不会被修改。

请注意,append 确实List 模块上的静态方法。 在单独的模块中编写“转换”方法鼓励无副作用的设计,因为根据定义没有内部存储可用,即使语言允许(F# 允许,LISP 不允许)。但是没有什么能真正阻止您编写无副作用的非静态方法

最后,如果您想深入了解函数式语言概念,请使用一个! 编写操作不可变 F# 数据结构的 F# 模块要比在 C# 中使用或不使用静态方法模仿相同的方法要自然得多.

【讨论】:

  • 感谢 Dan,您似乎在这里给出了最全面和面向问题的答案。也感谢您编辑问题标题,现在更清楚了。希望我可以决定用 F# 编写我们的数据处理代码,但我们都是 C# 哦,太好了。仍然对那些在调用者框架中执行静态方法的时候感到好奇。也许我在想象事情......
  • 您可以编写完全合理的实例方法,而不会产生真正确实需要成为实例方法的副作用。如果他们只inspect 字段,而不是修改它们,那么它们可以没有副作用。大量纯函数式 Scala 代码正是以这种方式工作的。 “无副作用”编程只是意味着您的调用结果仅取决于它们的输入,并且具体不受事先进行的其他调用的影响。正如您所说,您可以轻松编写具有副作用的静态方法。静态性与无副作用几乎没有关系。
【解决方案2】:

CLR 确实做了一些尾调用优化,但仅限于 64 位 CLR 进程。请参阅以下内容了解其完成位置:David Broman's CLR Profiling API Blog: Tail call JIT conditions

至于仅使用静态变量和局部范围构建软件,我已经做了很多,而且实际上还不错。这只是另一种与 OO 一样有效的做事方式。实际上因为在函数/闭包之外没有状态,所以更安全,更容易测试。

不过,我先从头到尾阅读了整本 SICP 书:http://mitpress.mit.edu/sicp/

没有副作用只是意味着可以使用相同的参数多次调用该函数,并且始终返回相同的值。这只是定义了函数的结果始终是一致的,因此不依赖于任何外部状态。因此,并行化函数、缓存它、测试它、修改它、装饰它等等都是微不足道的。

但是,没有副作用的系统通常是无用的,所以做 IO 的东西总会有副作用。它使您可以巧妙地封装其他所有内容,但这就是重点。

不管人们怎么说,对象并不总是最好的方式。事实上,如果您曾经使用过 LISP 变体,那么您无疑会确定典型的 OO 有时确实会妨碍您。

【讨论】:

  • 所以从你的解释来看,如果只有静态方法和局部范围变量,那将是“没有副作用”的编程?
  • 这在 C# 编程语言的范围内是正确的。您可以使用不可变类型(在每次操作中总是复制并返回新实例的类型)获得一些额外的里程,但它们在 .Net 中并不常见,除了字符串类型。
  • 只是好奇:如果您只使用静态变量和局部范围进行编程,为什么要使用 C# 而不是更适合函数式编程的语言,如 F# 或许多非 CLR 语言?
  • @Matthew:我愿意(我使用我自己用 C 编写的方案实现)但不幸的是,我工作的地方只要求 C#,甚至不是 F#。
  • @Chris:我认为副作用编程在 C# 中是不自然的,并且会导致您的同事可能不会欣赏的代码。我永远不会为生产代码这样做。
【解决方案3】:

有一本关于这个主题的很好的书,http://www.amazon.com/Real-World-Functional-Programming-Examples/dp/1933988924

不幸的是,由于团队技能或现有代码库,在现实世界中使用 F# 不是一个选项,这也是我喜欢这本书的另一个原因,因为它展示了在你使用的代码中实现 F# 功能的许多方法今天。至少对我来说,状态错误的大量减少(调试时间比简单的逻辑错误要长得多)值得稍微减少 OOP 正统观念。

在大多数情况下,没有静态状态并且仅在给定参数上以静态方法运行将消除副作用,因为您将自己限制为纯函数。需要注意的一点是在这样的函数中检索要操作的数据或将数据保存到数据库中。但是,结合 OOP 和静态方法在这里可以有所帮助,方法是让您的静态方法委托给较低级别​​的对象命令来操作状态。

在执行函数纯度方面也有很大帮助,那就是尽可能保持对象不可变。任何作用于的对象都应该返回一个新的修改实例,并丢弃原始副本。

【讨论】:

    【解决方案4】:

    关于第二个问题:我相信您的意思是可变数据结构的“副作用”,显然这对于​​(我相信)大多数函数式语言来说都不是问题。例如,Haskel 大部分(甚至全部!?)使用不可变数据结构。所以没有什么关于“静态”行为的。

    【讨论】:

      猜你喜欢
      • 2013-06-12
      • 2016-05-03
      • 1970-01-01
      • 2021-03-24
      • 2011-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多