向面向对象的程序员和技术人员解释函数式编程

rsi*_*deb 22 oop functional-programming

我可以用什么好的例子来解释函数式编程?

观众将是具有少量编程经验的人,或者仅具有面向对象体验的人.

Jul*_*iet 31

观众将是那些编程经验不足的人,

是否真的有可能解释函数式编程,让OO或程序或任何编程范例给没有太多编程经验的人?

或者只有面向对象经验的人.

可能最好的例子是将已知的设计模式转换为功能等同物.我们来看一个将int列表转换为字符串列表的典型示例:

using System;

namespace Juliet
{
    interface IConvertor<T, U>
    {
        U Convert(T value);
    }

    class Program
    {
        static U[] Convert<T, U>(T[] input, IConvertor<T, U> convertor)
        {
            U[] res = new U[input.Length];
            for (int i = 0; i < input.Length; i++)
            {
                res[i] = convertor.Convert(input[i]);
            }
            return res;
        }

        class SquareInt : IConvertor<int, string>
        {
            public string Convert(int i)
            {
                return (i * i).ToString();
            }
        }

        class ScaleInt : IConvertor<int, string>
        {
            readonly int Scale;

            public ScaleInt(int scale)
            {
                this.Scale = scale;
            }

            public string Convert(int i)
            {
                return (i * Scale).ToString();
            }
        }

        static void Main(string[] args)
        {
            int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            string[] squared = Convert<int, string>(nums, new SquareInt());
            string[] tripled = Convert<int, string>(nums, new ScaleInt(3));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它简单,可读,面向对象,甚至通用,因此适用于任意类型,所以问题是什么?首先,它臃肿:我有一个接口定义和两个接口实现.如果我需要另一次转换怎么办?好吧,我需要另一个接口实现 - 它可以快速失控.

当你考虑它时,这个IConvertor<T, U>类只是一个被称为单个函数的包装器Convert- 这个类实际上是为了帮助我们传递Convert给其他函数.如果您能理解这一点,那么您已经将函数背后的基本原理理解为一流的值 - 函数式编程就是将函数传递给其他函数,就像传递一个人或一个int或一个字符串一样.

人们通常更喜欢函数式编程,因为它可以帮助他们避免单方法接口和实现.我们只是通过名称或匿名传递函数,而不是传递类:

using System;

namespace Juliet
{
    class Program
    {
        static U[] Convert<T, U>(T[] input, Func<T, U> convertor)
        {
            U[] res = new U[input.Length];
            for (int i = 0; i < input.Length; i++)
            {
                res[i] = convertor(input[i]);
            }
            return res;
        }

        static string SquareInt(int i)
        {
            return (i * i).ToString();
        }

        static void Main(string[] args)
        {
            int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            string[] squared = Convert<int, string>(nums, SquareInt); // pass function by name
            string[] tripled = Convert<int, string>(nums, i => (i * 3).ToString()); // or pass anonymously
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

好吧,现在我们在更少的代码行中拥有完全相同的程序,它的可读性,以及它的作用非常明显.

现在有人可能会说"这是一个巧妙的技巧,但我什么时候才能使用它" - 有很多情况下你想要像这样传递函数.它为您提供了更多的功能,以新颖的方式抽象您的程序控制流程.根据此处显示的示例进行改编,考虑一个处理文件的类:

class FileFunctions
{
    internal void SaveFile()
    {
        SaveFileDialog fileDialog = new SaveFileDialog();
        fileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
        if (fileDialog.ShowDialog() == DialogResult.OK)
        {
            File.AppendAllText(fileDialog.FileName, MyDocument.Data);
        }
    }

    internal void WriteFile()
    {
        OpenFileDialog fileDialog = new OpenFileDialog();
        fileDialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
        if (fileDialog.ShowDialog() == DialogResult.OK)
        {
            MyDocument.Data = File.ReadAllText(fileDialog.FileName);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

呸.代码是重复的,但它在不同的类上执行操作并调用不同的方法.你如何抽象出OO宇宙中的重复代码?在功能编程中,它是微不足道的:

class FileFunctions
{
    internal void ShowDialogThen(FileDialog dialog, Action<FileDialog> f)
    {
        dialog.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*";
        if (dialog.ShowDialog() == DialogResult.OK)
        {
            f(dialog);
        }
    }

    internal void SaveFile()
    {
        ShowDialogThen(
            new SaveFileDialog(),
            dialog => File.AppendAllText(dialog.FileName, MyDocument.Data));
    }

    internal void WriteFile()
    {
        ShowDialogThen(
            new OpenFileDialog(),
            dialog => { MyDocument.Data = File.ReadAllText(dialog.FileName); });
    }
}
Run Code Online (Sandbox Code Playgroud)

真棒.


Rob*_*vey 9

首先通过其基本特征来描述函数式编程可能更容易:

  1. 功能(显然)
  2. 没有副作用
  3. 不变性
  4. 递归非常重要
  5. 非常可并行化

其中,最重要的特征是没有副作用的概念,因为其他特征也是如此.

功能编程用于您可能不期望的地方.例如,它用于运行交通信号灯和通信交换机(爱立信交换机运行Erlang).


out*_*tis 5

那些编程经验很少的人可能更容易,因为他们没有那么多的编程概念妨碍:函数式编程非常类似于数学函数作为表达式,所以向他们展示一些数学.

对于OO人群,您可以展示闭包是如何封装的,以及函数式编程如何包含OOP.您还可以展示高阶函数的有用性,特别是它们如何导致一些OOP模式(例如策略和模板模式,以及使用多方法的语言中的访问者模式)逐渐淡化为语言(Peter Norvig断言 16 在Dylan和Lisp中,23种GOF模式更简单.作为对应物,基于消息传递的OOP(而不是CLOS方法)是FP中的一种模式,在OOP中是不可见的.

证明编写通用算术表达式以及函数组合和区分是多么容易,因为编程构造并且你已经得到了它们.

你可能想也可能不想向OO人展示CLOS的多方法; 他们要么是暴动,要么是怪胎.无论哪种方式,它都可能是凌乱的.

显示灵活FP的其他一些可能性:可以使用Nullary匿名函数实现延迟评估.FP可以支持基于类的OOP和基于原型的OOP.还有很多其他的例子(例如延续传递风格),但他们通常需要在学习之前熟悉FP.

SICP有很多有趣的例子和问题; 它应该证明是鼓舞人心的.