函数式编程最好/最差解决哪些问题?

Posted

技术标签:

【中文标题】函数式编程最好/最差解决哪些问题?【英文标题】:What are some problems best/worst addressed by functional programming? 【发布时间】:2010-11-03 04:03:43 【问题描述】:

我经常听说函数式编程解决了很多过程式/命令式编程中难以解决的问题。但我也听说它不擅长处理程序编程天生擅长的其他一些问题。

在我打开关于 Haskell 的书并深入研究函数式编程之前,我想至少了解一下我真正可以将它用于什么的基本概念(除了书中的示例)。那么,函数式编程擅长什么?有哪些不适合的问题?

更新

到目前为止,我已经得到了一些很好的答案。我等不及要开始学习 Haskell 了——我只需要等到我掌握 C :)

函数式编程之所以伟大的原因:

非常简洁明了 - 它可以用简短、清晰的语句表达复杂的想法。 比命令式语言更容易验证 - 在系统中的安全性至关重要的情况下很好。 函数的纯粹性和数据的不变性使并发编程更加合理。 非常适合编写脚本和编写编译器(但我很想知道原因)。 数学相关的问题解决得简单又漂亮。

函数式编程难以解决的领域:

值得商榷:网络应用程序(虽然我猜这取决于应用程序)。 桌面应用程序(虽然它可能取决于语言,但 F# 会擅长这不是吗?)。 任何对性能至关重要的事物,例如游戏引擎。 任何涉及大量程序状态的事物。

【问题讨论】:

总和 [ x | x 我不明白为什么桌面应用程序会成为问题,可能除了来自 GUI 构建器工具等的较低级别的支持。 “很多程序状态”可能意义不大。在许多情况下,您可以用函数、闭包和延续的正确组合来替换看似复杂的程序状态。 取决于您使用的“函数式编程”的定义。您是指像 Haskell 中的纯函数式还是像 F#、OCaml、Scala、Clojure 甚至 C# 3.0 中那样提供一流的词法闭包? 我只是指一般的功能范式 我不得不不同意你的反对部分的一切。函数式编程(实际上是范式)非常适合 Web 应用程序(Haskell+Happstack/Snap/Yesod、Racket+CPS 等)。带有 FRP 的 Haskell 比我所见过的用于 GUI/桌面应用程序的任何东西都要好,它只是需要更多的开发人员来处理它。我经常为你能想象到的一切(数字运算、模拟、人工智能、图形、网络等)编写速度极快的 Haskell 程序。最后,Haskell(同样是 FRP)是维护复杂状态的最佳语言。见***.com/q/15467925/1978898 【参考方案1】:

由于higher level functions (map, lfold, grep) 和type inference 的存在,函数式编程擅长简洁。

generic programming 也很出色,出于同样的原因,这进一步提高了用简短的语句表达复杂想法而不会混淆的能力。

我很欣赏这些属性,因为它们使interactive programming 合理。 (例如R、SML)。

我怀疑函数式编程也比其他编程方法更容易验证,这在安全关键系统(核电站和医疗设备)中是有利的。

【讨论】:

类型推断不是函数式编程的属性。 en.wikipedia.org/wiki/Type_inference “它通常是——但不限于——一般的函数式编程语言的特征。” Antony J. T. Davie 1996 年使用 Haskell 介绍函数式编程系统:“函数式编程语言最重要的思想之一是自动类型推断”你的证据在哪里? Haskell 的作者认为类型推断是 FPL 的一个重要属性,这不足为奇。如果没有静态类型系统,它会更短。想想 APL - 不,APL 程序之所以短,不仅是因为标识符。 并非所有函数式语言都具有类型推断,并且存在具有类型推断的命令式语言。 命令式语言可以有高阶函数【参考方案2】:

我会说函数式编程适合解决问题,例如AI 问题、数学问题(这太容易了)、游戏引擎,但不太适合 GUI 和自定义控件开发或需要精美 UI 的桌面应用程序。我发现以以下方式思考很直观(尽管它可能过于笼统):

            Back-end   Front-end
Low-level   C          C++
High-level  FP         VB, C#

【讨论】:

这是一种非常简单的看待它的方式——函数式编程就像一个高级别的,但是是后端实现。谢谢 我几乎给你+1,直到我注意到VB 列在其中。不,我认为你没有错,但我就是那么讨厌 VB。 我会给你一个 +1 的 Rosdi。漂亮的桌子。【参考方案3】:

函数式编程适合并行编程。您不依赖函数式编程的状态更改这一事实意味着各种处理器/内核不会相互影响。因此,能够很好地适应并行性的算法类型,如压缩、图形效果和一些复杂的数学任务,通常也很适合函数式编程。而多核 CPU 和 GPU 越来越受欢迎的事实也意味着对这类东西的需求将会增长。

【讨论】:

-1。您隐含地指的是纯粹的函数式编程(避免副作用,而不是使用一流的函数),这在性能方面完全是一场灾难,而并行性就是关于性能的。 AFAIK Haskell 的速度与大多数更好的编程语言差不多,所以这绝对不算数。【参考方案4】:

它可能与函数式编程没有直接关系,但在数据结构的设计和实现中没有什么比联合更好的了。让我们比较两段等效的代码:

F#:

type 'a stack = Cons of 'a * stack |零 让 rec to_seq = 函数 |无 -> Seq.empty; |缺点(hd,tl)-> seq yield hd;屈服! to_seq tl 让 rec 附加 x y = 将 x 与 |无 -> 是 | Cons(hd, tl) -> Cons(hd, append tl y) 让 x = Cons(1, Cons(2, Cons(3, Cons(4, Nil)))) 让 y = Cons(5, Cons(6, Cons(7, Cons(8, Nil)))) 让 z = 附加 x y to_seq z |> Seq.iter (fun x -> printfn "%i" x)

Java:

interface IStack<T> extends Iterable<T>

    IStack<T> Push(T value);
    IStack<T> Pop();
    T Peek();
    boolean IsEmpty();

final class EmptyStack<T> implements IStack<T>

    public boolean IsEmpty()  return true; 
    public IStack<T> Push(T value)  return Stack.cons(value, this); 
    public IStack<T> Pop()  throw new Error("Empty Stack"); 
    public T Peek()  throw new Error("Empty Stack"); 
    public java.util.Iterator<T> iterator()
    
        return new java.util.Iterator<T>()
        
            public boolean hasNext()  return false; 
            public T next()  return null; 
            public void remove()  
        ;
    

final class Stack<T> implements IStack<T>

    public static <T> IStack<T> cons(T hd, IStack<T> tl)  return new Stack<T>(hd, tl); 
    public static <T> IStack<T> append(IStack<T> x, IStack<T> y)
    
        return x.IsEmpty() ? y : new Stack(x.Peek(), append(x.Pop(), y));
    
    private final T hd;
    private final IStack<T> tl;
    private Stack(T hd, IStack<T> tl)
    
        this.hd = hd;
        this.tl = tl;
    
    public IStack<T> Push(T value)  return new <T> Stack(value, this); 
    public IStack<T> Pop()  return this.tl; 
    public T Peek()  return this.hd; 
    public boolean IsEmpty()  return false; 
    public java.util.Iterator<T> iterator()
    
        final IStack<T> outer = this;
        return new java.util.Iterator<T>()
        
            private IStack<T> current = outer;
            public boolean hasNext()  return !current.IsEmpty(); 
            public T next()
            
                T hd = current.Peek();
                current = current.Pop();
                return hd;
            
            public void remove()  
        ;
    

public class Main

    public static void main(String[] args)
    
        IStack<Integer> x = Stack.cons(1, Stack.cons(2, Stack.cons(3, Stack.cons(4, new EmptyStack()))));
        IStack<Integer> y = Stack.cons(5, Stack.cons(6, Stack.cons(7, Stack.cons(8, new EmptyStack()))));
        IStack<Integer> z = Stack.append(x, y);

        for (Integer num : z)
        
            System.out.printf("%s ", num);
        
    

【讨论】:

这就是 boost::variant 的用途:-) 那不一样了哈哈 我认为,这更多是稳健类型系统的结果,而不是特定于函数式编程的结果。另一方面,在函数式编程环境之外没有太多真正好的类型系统,也没有太多没有仔细考虑类型系统的函数式编程环境。 -- 尤其是 Java 是一个糟糕的例子,设计师从现有模型 (C++) 开始并删除了他们不喜欢的东西,而不是将类型系统设计为具有表现力和可读性 !【参考方案5】:

我发现函数式编程适合的一些问题:

并发 编译器 脚本

我个人认为不太适合的问题:

Web 应用程序(但这可能只是我,Hacker News 例如在 LISP 中实现) 桌面应用程序 游戏引擎 你在很多州之间传递的东西

【讨论】:

我发现使用 FP 编写脚本有点奇怪。脚本通常都足够小以至于状态不会咬你。 如果我说 FP 中的脚本,我认为尤其是 Scheme 和 LISP。一个广泛使用的例子是 Emacs LISP。 javascript 最初是作为 Scheme 开始的,它在这里和那里展示。【参考方案6】:

我发现 Haskell 非常适合做任何与数学相关的事情。并不是说这是一个真正的专业项目,而是我用它制作了一个数独求解器和扑克分析器。拥有一个在数学上可证明的程序很棒。

就它不适合的任何事情而言,性能是优先考虑的事情。您对所使用的算法的控制较少,因为它更具声明性而不是命令性。

【讨论】:

我不太同意。为了获得性能,您通常必须打破抽象,从硬件的角度弄清楚各种高级构造是如何实际实现的。当然 Haskell 被进一步抽象了,但是如果你努力学习 GHC 的实现细节,优化性能是完全可行的。其他函数式语言(如 Lisp 和 SML)具有更简单的执行和内存模型,并且可以像传统的命令式语言一样轻松“调整”。 但是,这就是问题的一部分:你提到了 GHC。尽管它是我最喜欢的 Haskell 实现,但还有其他实现,而且可能还会有更多。更重要的是,不能保证一个实现的核心库函数的实现在不同版本之间保持相同。这就是为什么我认为最好在“什么”比“如何”更重要的情况下使用函数式语言。 鉴于没有其他高性能的 Haskell 环境,我认为以 GHC 为目标并不太不合理……人们在尝试优化 C 时专门针对 GCC 或 ICC 或 MSVC 等代码,不是吗? 还不错;但你必须承认,C 代码比 Haskell 更直接地转换为机器指令,因此如何优化 C 算法可能更明显。但是,当然,当您刚开始时,必须对您的代码投入更多的思考与 Haskell 相关。在大多数情况下,您不必担心速度,因此优雅是值得的。 理论上,在很多情况下,纯函数式编程语言应该比命令式编程语言更快,因为它可以进行更多优化。但是 GHC 不像 GCC 那样成熟,所以并不是所有的优化都已经到位。此外,Haskell 的水平相当高,所以也有点伤它。但我认为这是速度与优雅的最佳权衡之一。【参考方案7】:

我不同意 FP 不能用于 Web 应用程序。我知道 Paul Grahm 和 Robert Morris 创立了使用 Lisp 交付 Web 应用程序的 Viaweb。我认为随着您更接近硬件(设备驱动程序、内核等),人们希望尽可能使用低级语言。这是因为如果使用更多的抽象,那么在出现错误时更难调试。查看 Joel Spolsky 的文章“泄漏抽象定律”。

【讨论】:

Common Lisp 不是一种严格的函数式语言,尽管用它编写函数式程序很容易。我不知道 Viaweb 采用了哪种 Lisp 编程。 我认为这一点值得商榷,尽管没有说 FP 不能 用于 Web 应用程序。只有另一种范式更适合处理该任务——我自己从未开发过 Web 应用程序,所以我无法真正讨论它,但我真正要寻找的是 FP 是 正确 (或错误的)选择并提供比命令式编程显着的优势。您认为 Web 应用程序是其中一种情况吗? 连我都没有开发过网络应用程序。然而,Paul Graham 肯定声称在该应用程序中使用 Lisp 是一个主要优势。你可以在这里查看他的文章paulgraham.com/avg.html 那是因为他正在寻找的替代方案是 C Web 应用程序。如果他的竞争对手一直在使用当今的 Ruby 和 Python 环境,我认为他不会看到 Lisp 的优势。【参考方案8】:

实际上,我喜欢使用纯函数式代码来解决需要管理大量状态的问题。这些语言倾向于为显式处理状态提供最佳机制,因为它们不允许您隐式执行。在小范围内隐式管理状态似乎更容易,但是一旦状态开始变得复杂,您就会遇到麻烦,而无法通过 FP 方式来保证正确性。

【讨论】:

【参考方案9】:

我发现使用矩阵数学来解决数学问题的函数式编程的简单性绝对是美妙的,看看这些类型的问题在 Scheme 中是如何解决的!

【讨论】:

【参考方案10】:

我会说函数式编程对于低级的东西、操作系统内核、设备驱动程序等都会有问题。

我说的是“有麻烦”,而不是“不能用”(因为图灵等价,任何东西都可以用于任何事情)。

一个有趣的问题是这个问题是否是函数式编程的基础(因为物理设备具有状态),或者我们是否可以想象面向系统的函数式编程语言/环境。例如,BitC 只是部分功能(它在很大程度上依赖于可变性)。

【讨论】:

(有用Haskel写的操作系统)【参考方案11】:

函数式和面向对象的范式具有正交强度。 您可以说函数式编程强调动词和面向对象的名词编程。 或者,更实际地说: 面向对象使添加新数据变得简单,而函数式编程使添加新工具变得简单。 两者都需要更改代码以实现另一个目标。 您想阅读http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.18.4525&rep=rep1&type=pdf(综合面向对象和函数式设计以促进重用

【讨论】:

以上是关于函数式编程最好/最差解决哪些问题?的主要内容,如果未能解决你的问题,请参考以下文章

js函数式编程-柯里化

哪些编程语言是函数式的?

函数式编程简介

函数式编程思想:耦合和组合,第1部分

纯函数式编程

前端必学-函数式编程(六)