我有两个问题,源于观察到的C#静态方法的行为(我可能会误解):
第一: 递归静态方法在某种意义上是通过静态方法在封面下实现的方式进行尾调用优化吗?
第二: 它是否等同于函数式编程用静态方法编写整个应用程序而没有超出局部范围的变量?我很想知道,因为我仍然没有把这个“没有副作用”的术语包裹起来,我一直听到有关函数式编程的信息。
编辑: 让我提一下,我确实使用并理解为什么以及何时在普通的C#OO方法中使用静态方法,并且我确实理解尾部调用优化不会明确地对递归静态方法进行。也就是说,我理解尾部调用优化是尝试在每次传递时停止创建新的堆栈帧,并且我在几个点观察到在它的调用方法的框架内执行的似乎是一个静态方法,尽管我可能误解了我的观察。
答案 0 :(得分:6)
在某种意义上,递归静态方法是否会通过静态方法在封面下实现的方式进行尾调用优化?
静态方法与尾递归优化无关。所有rules同样适用于实例和静态方法,但我个人从不依赖JIT优化了我的尾调用。而且,C# compiler doesn't emit tail call instruction but sometimes it is performed anyway。简而言之,你永远不会知道。
F#编译器支持尾递归优化,并在可能的情况下编译递归到循环 在此question中查看有关C#与F#行为的更多详细信息。
使用静态方法编写整个应用程序并且没有超出局部范围的变量,是否等同于函数式编程?
no 和是。
从技术上讲,没有什么可以阻止你从静态方法(这是一个静态方法本身!)中调用Console.WriteLine
,这显然具有副作用。没有什么能阻止您编写不改变任何状态的类(使用实例方法)(即实例方法不访问实例字段)。但是从设计的角度来看,这样的方法并不像实例方法那样有意义,对吗?
如果您Add
项目为.NET Framework List<T>
(有副作用),您将修改其状态。
如果您append
项目为F# list,您将获得另一个列表,并且不会修改原始文件。
请注意,append
确实 是List
模块上的静态方法。 在单独的模块中编写“转换”方法鼓励无副作用的设计,因为没有内部存储可用定义,即使语言允许它(F#,LISP不允许)。然而没有什么能阻止你编写一个无副作用的非静态方法。
最后,如果你想要理解功能语言概念,请使用一个!编写运行不可变F#数据结构的F#模块比使用或不使用静态方法在C#中模仿相同更自然。
答案 1 :(得分:3)
CLR确实进行了一些尾调用优化,但仅限于64位CLR进程。请参阅以下内容:David Broman's CLR Profiling API Blog: Tail call JIT conditions。
至于构建只有静态变量和局部范围的软件,我已经做了很多,实际上很好。这只是与OO一样有效的另一种做事方式。事实上,因为函数/闭包之外没有状态,所以测试更安全,更容易。
我从头到尾阅读了整本SICP书籍,但是:http://mitpress.mit.edu/sicp/
没有副作用只是意味着可以根据需要使用相同的参数调用函数,并始终返回相同的值。这简单地定义了函数的结果总是一致的,因此不依赖于任何外部状态。因此,将函数并行化,缓存,测试,修改,装饰等都是微不足道的。
然而,没有副作用的系统通常是无用的,因此做IO的事情总会产生副作用。它允许你巧妙地封装其他所有内容,但重要的是它。
尽管人们说什么,对象并不总是最好的方式。事实上,如果你曾经使用过LISP变种,你无疑会确定典型的OO确实会妨碍你。
答案 2 :(得分:1)
关于第二个问题:我认为你的意思是可变数据结构的“副作用”,显然这不是(我相信)大多数功能语言的问题。例如,Haskel主要(甚至全部!?)使用不可变数据结构。所以没有任何关于“静态”的行为。
答案 3 :(得分:1)
有一本关于这个主题的好书,http://www.amazon.com/Real-World-Functional-Programming-Examples/dp/1933988924。
在使用F#的现实世界中,由于团队技能或现有代码库,这不是一个选择,这是我喜欢这本书的另一个原因,因为它已经显示了许多方法在您使用的代码中实现F#功能到了一天。至少对我来说,状态错误的大量减少,比简单的逻辑错误需要更长的时间来调试,值得OOP正统的略微减少。
大多数情况下,没有静态状态并且只使用给定的参数在静态方法中操作将消除副作用,因为您将自己局限于纯函数。值得注意的一点是,在这样的功能中检索要作用的数据或将数据保存到数据库。但是,通过将静态方法委托给较低级别的对象命令来操作状态,将OOP和静态方法结合起来可以提供帮助。
在执行函数纯度方面,一个很大的帮助就是尽可能保持对象不可变。任何被操作的对象都应返回一个新的修改过的实例,并丢弃原始副本。