【问题标题】:Functional programming and non-functional programming函数式编程和非函数式编程
【发布时间】:2010-09-06 15:50:31
【问题描述】:

在我大学的第二年,我们“教”了 Haskell,我对此几乎一无所知,对函数式编程更是一无所知。

什么是函数式编程,为什么和/或我想在哪里使用它而不是非函数式编程,我认为 C 是一种非函数式编程语言是否正确?

【问题讨论】:

    标签: functional-programming paradigms glossary


    【解决方案1】:

    我更喜欢使用函数式编程来节省自己的重复工作,方法是制作一个更抽象的版本,然后改用它。让我举个例子。在 Java 中,我经常发现自己创建映射来记录结构,并因此编写 getOrCreate 结构。

    SomeKindOfRecord<T> getOrCreate(T thing) { 
        if(localMap.contains(thing)) { return localMap.get(thing); }
        SomeKindOfRecord<T> record = new SomeKindOfRecord<T>(thing);
        localMap = localMap.put(thing, record);
        return record; 
    }
    

    这种情况经常发生。现在,我可以用函数式语言编写

    RT<T> getOrCreate(T thing, 
                      Function<RT<T>> thingConstructor, 
                      Map<T,RT<T>> localMap) {
        if(localMap.contains(thing)) { return localMap.get(thing); }
        RT<T> record = thingConstructor(thing);
        localMap = localMap.put(thing,record);
        return record; 
    }
    

    我再也不用写新的了,我可以继承它。但是我可以做得比继承更好,我可以在这个东西的构造函数中说

    getOrCreate = myLib.getOrCreate(*,
                                    SomeKindOfRecord<T>.constructor(<T>), 
                                    localMap);
    

    (其中 * 是一种“保持此参数打开”表示法,这是一种柯里化)

    然后本地的 getOrCreate 与我在一行中写出整个内容时完全一样,没有继承依赖关系。

    【讨论】:

      【解决方案2】:

      我觉得What Is Functional Programming? 很有用

      函数式编程是关于编写纯函数,关于删除 尽可能隐藏输入和输出,这样我们的 代码尽可能只描述输入和之间的关系 输出。

      首选显式when参数

      public Program getProgramAt(TVGuide guide, int channel, Date when) {
        Schedule schedule = guide.getSchedule(channel);
      
        Program program = schedule.programAt(when);
      
        return program;
      }
      

      结束

      public Program getCurrentProgram(TVGuide guide, int channel) {
        Schedule schedule = guide.getSchedule(channel);
      
        Program current = schedule.programAt(new Date());
      
        return current;
      }
      

      函数式语言对副作用非常不利。副作用是复杂性,复杂性是错误,错误是魔鬼。函数式语言也可以帮助您避免副作用。

      【讨论】:

        【解决方案3】:

        什么是函数式编程

        当今常用的“函数式编程”有两种不同的定义:

        较早的定义(源自 Lisp)是函数式编程是关于使用一等函数进行编程,即函数被视为任何其他值,因此您可以将函数作为参数传递给其他函数,并且函数可以在它们之间返回函数返回值。这最终导致使用诸如mapreduce 之类的高阶函数(您可能听说过mapReduce 是Google 大量使用的单个操作,毫不奇怪,它是近亲!)。 .NET 类型 System.FuncSystem.Action 使 C# 中的高阶函数可用。尽管 currying 在 C# 中不切实际,但接受其他函数作为参数的函数很常见,例如Parallel.For 函数。

        较年轻的定义(由 Haskell 推广)是函数式编程也是关于最小化和控制包括变异在内的副作用,即编写通过组合表达式来解决问题的程序。这通常被称为“纯函数式编程”。这可以通过称为“纯功能数据结构”的数据结构的完全不同的方法来实现。一个问题是,将传统的命令式算法转换为使用纯函数式数据结构通常会使性能降低 10 倍。 Haskell 是唯一幸存的纯函数式编程语言,但这些概念已经通过 .NET 上的 Linq 等库进入主流编程。

        我想在哪里使用它而不是非函数式编程

        无处不在。 C# 中的 Lambda 现在已经展示了主要的好处。 C++11 有 lambda。现在没有理由不使用高阶函数。如果您可以使用像 F# 这样的语言,您还将受益于类型推断、自动泛化、柯里化和部分应用程序(以及许多其他语言功能!)。

        我认为 C 是一种非函数式编程语言是否正确?

        是的。 C是一种过程语言。但是,您可以通过在 C 中使用函数指针和 void * 来获得函数式编程的一些好处。

        【讨论】:

        • 您能否详细说明这一行,或者分享一个示例:“一个问题是,将传统的命令式算法转换为使用纯函数式数据结构通常会使性能降低 10 倍。”
        • 几乎所有传统算法(例如快速排序、LZW、LU 分解、Prim 的 MST)在本质上几乎总是必然的。具体来说,它们永远不会从持久性中受益:它们总是只在集合的最新版本上运行,从不重用旧版本。这使得它们非常适合使用传统的可变集合来实现,但这意味着如果你使用纯函数集合来实现它们,那么你会为持久性付出代价(即速度慢),但没有任何好处。
        • 相反,您必须更仔细地寻找能够解决相同问题并确实受益于持久性的奇异算法,例如归并排序和 Borůvka 的 MST。
        【解决方案4】:

        John the Statistician 的示例代码没有显示函数式编程,因为当您进行函数式编程时,关键是代码没有分配(record = thingConstructor(t) 是分配),并且没有副作用(@987654323 @ 是一个有副作用的语句)。由于这两个约束,函数所做的一切都被它的参数和返回值完全捕获。如果您想使用 C++ 模拟一种函数式语言,请按照它的外观重写 Statistician 的代码:

        RT getOrCreate(const T 的东西, const Function> thingConstructor, const Map> localMap) { 返回 localMap.contains(t) ? localMap.get(t) : localMap.put(t,thingConstructor(t)); }

        由于没有副作用规则,每个语句都是返回值的一部分(因此return 首先出现),每个语句都是一个表达式。在强制执行函数式编程的语言中,隐含了 return 关键字,并且 if 语句的行为类似于 C++ 的 ?: 运算符。

        而且,一切都是不可变的,所以localMap.put 必须创建一个新的 localMap 副本并将其返回,而不是像普通的那样修改原始 localMap C++ 或 Java 程序会。根据 localMap 的结构,副本可以重用指向原始数据的指针,从而减少必须复制的数据量。

        函数式编程的一些优点包括函数式程序更短,更容易修改函数式程序(因为没有隐藏的全局影响需要考虑),并且更容易获得程序第一时间。

        但是,函数式程序往往运行缓慢(因为它们必须进行所有复制),并且它们往往不能与处理内存地址的其他程序、操作系统进程或操作系统进行良好的交互, little-endian 字节块和其他特定于机器的非功能位。不可互操作性的程度往往与功能纯度和类型系统的严格程度成反比。

        更流行的函数式语言具有非常非常严格的类型系统。在 OCAML 中,您甚至不能混合使用整数和浮点数学,或者使用相同的运算符(+ 用于添加整数,+. 用于添加浮点数)。这可能是优点也可能是缺点,这取决于您对类型检查器捕获某些类型错误的能力的重视程度。

        函数式语言也往往具有非常大的运行时环境。 Haskell 是一个例外(GHC 可执行文件在编译时和运行时几乎和 C 程序一样小),但 SML、Common Lisp 和 Scheme 程序总是需要大量内存。

        【讨论】:

        • 我不同意,因为函数式语言可以(并且,像 Erlang,偶尔会这样做)允许没有副作用的单一分配。但是,您关于副作用的观点是正确的。不过,我不会更改我的代码,因为我认为说明混合语言中功能特性的优势比严格的纯度更重要。
        • 绝大多数函数式编程都是关于使用一等函数,而不是关于禁止副作用。我的测量表明您关于运行时环境大小的陈述是错误的:GHC 生成的二进制文件是 OCaml 和标准 ML(使用 MLton)大小的两倍,是 HLVM 大小的 20 倍。
        • 不要将 FP 与无副作用混为一谈,即与 DP vs. IP。那是 FP。
        【解决方案5】:

        如果您正在 F# 上寻找好的文本

        Expert F# 由 Don Syme 共同撰写。 F#的创造者。他专门研究 .NET 中的泛型,以便创建 F#。

        F# 以 OCaml 为模型,因此任何 OCaml 文本都可以帮助您学习 F#。

        【讨论】:

        • Don 真的只是为 F# 做泛型吗?我觉得这非常令人惊讶!
        【解决方案6】:

        函数式语言的一个关键特性是一等函数的概念。这个想法是您可以将函数作为参数传递给其他函数并将它们作为值返回。

        函数式编程涉及编写不改变状态的代码。这样做的主要原因是对函数的连续调用将产生相同的结果。您可以使用任何支持一流函数的语言编写函数式代码,但是有些语言(例如 Haskell)不允许您更改状态。事实上,你根本不应该产生任何副作用(比如打印文本)——这听起来可能完全没用。

        Haskell 采用了不同的 IO 方法:monads。这些对象包含要由解释器的顶层执行的所需 IO 操作。在任何其他级别,它们只是系统中的对象。

        函数式编程提供了哪些优势?函数式编程允许以更少的潜在错误进行编码,因为每个组件都是完全隔离的。此外,使用递归和一等函数可以简单地证明正确性,这通常反映了代码的结构。

        【讨论】:

        • “这样做的主要原因是连续调用一个函数会产生相同的结果”Why do we want successive calls to a function to yield the same result. What is the problem with the way things are in C? I never understood this.
        • @Lazer 不受欢迎的原因有几个。首先,如果一个函数返回相同的结果,那么您可以缓存该结果并在再次调用该函数时返回它。其次,如果函数没有返回相同的结果,这意味着它依赖于程序的某些外部状态,因此该函数不能轻易地预先计算或并行化。
        • 在分析和调试代码时也有很大的好处。副作用基本上只是对程序员隐藏的函数的隐式输入。
        • @MagnusKronqvist 如果它们是可变的,则它们与隐式输入不同,因为它们可以被写入。输入参数通常不能被写入(除非它们是引用类型)。区别的重要性在于访问可变的外部状态,使函数imperative and not declarative
        • “你可以用任何支持一流函数的语言编写函数式代码”——我要补充一点:1)在语言中支持类型类可以避免大量重复,2)Java 也可以表示第一类函数(使用单方法类对象,参见Guava的FunctionalJava的F函数),但语法过于冗长,无法使用。
        【解决方案7】:

        是的,您认为 C 是一种非函数式语言是正确的。 C 是一种过程语言。

        【讨论】:

          【解决方案8】:

          可能值得在最近发布的 CoDe Mag 上的 F# "101" 上查看这篇文章。

          另外,Dustin Campbell has a great blog 在那里他发布了许多关于他的冒险与 F# 速度的文章..

          我希望你觉得这些有用:)

          编辑:

          另外,补充一点,我对函数式编程的理解是,一切都是函数,或函数的参数,而不是实例/有状态对象。但我可能错了 F# 是什么我很想进去,但就是没有时间! :)

          【讨论】: