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

时间:2010-02-19 04:24:13

标签: oop functional-programming

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

观众将是那些没有编程经验的人,或者只有面向对象体验的人。

12 个答案:

答案 0 :(得分: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));
        }
    }
}

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

当你考虑它时,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
        }
    }
}

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

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

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);
        }
    }
}

呸。代码是重复的,但它在不同的类上执行操作并调用不同的方法。你如何抽象出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); });
    }
}

真棒。

答案 1 :(得分:9)

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

  1. 功能(显然)
  2. 没有副作用
  3. 不变性
  4. 递归非常重要
  5. 非常可并行化
  6. 其中,最重要的特征是没有副作用的概念,因为其他特征也是如此。

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

答案 2 :(得分:5)

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

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

证明编写通用算术表达式以及函数组合和区分是多么容易,因为编程结构和你有两个。

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

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

GOF patterns are simpler in Dylan and Lisp有很多有趣的例子和问题;它应该是鼓舞人心的。

答案 3 :(得分:3)

试着看看这篇名为Functional Programming for the Rest of U的文章,下面是摘录。

  

在本文中,我将使用Java编写的示例解释函数式语言中使用最广泛的想法(是的,如果你感觉特别自虐,你可以用Java编写函数式程序)。在接下来的几节中,我们将按原样使用Java,并对其进行修改以将其转换为可用的函数语言。让我们开始我们的任务。

答案 4 :(得分:3)

OO是关于名词,而函数式编程是关于动词。通过Steve Yegge阅读关于此主题的相当不错的博客。

答案 5 :(得分:3)

John Hughes的

Why Functional Programming Matters有一些很好的例子,但更好的是,它解释了为什么的函数式编程语言,而不仅仅是如何。你可以做比基于那篇论文构建演示文稿更糟糕的事情!

答案 6 :(得分:3)

Brian Marick的面向对象程序员的功能编程。我现在没有联系,也没有任何关联:

https://leanpub.com/fp-oo

鲍勃叔叔喜欢它! :)

答案 7 :(得分:2)

不可变数据结构

你知道,我之前已经向人们评论了函数式编程,并提出了整个“不可变数据结构”的事情。这通常会打击人们的想法 - 如果你不能真正添加​​它,你应该如何添加一些东西?

直接的答案是“你没有,你只需要添加对象创建一个新版本的数据结构”,立即响应“听起来效率不高”。好吧。

不可变堆栈

规范不可变数据结构是一个不可变堆栈,它基本上是一个包含两个只读值的节点:头部和尾部。从堆栈中推出和弹出是O(1)。列表中间的索引查找,插入/删除需要O(n)时间,顺便提一下,它与可变堆栈相同。

using System;

namespace Juliet
{
    public abstract class Stack<T>
    {
        public static readonly Stack<T> Empty = new Nil();

        public abstract T Head { get; }
        public abstract Stack<T> Tail { get; }
        public abstract bool IsEmpty { get; }
        public Stack<T> Push(T value) { return new Cons(value, this); }
        public Stack<T> Append(Stack<T> other)
        {
            if (this.IsEmpty) { return other; }
            else { return new Cons(this.Head, this.Tail.Append(other)); }
        }

        sealed class Nil : Stack<T>
        {
            public override T Head { get { throw new Exception("Empty stack"); } }
            public override Stack<T> Tail { get { throw new Exception("Empty stack"); } }
            public override bool IsEmpty { get { return true; } }
        }

        sealed class Cons : Stack<T>
        {
            private readonly T _head;
            private readonly Stack<T> _tail;

            public override T Head { get { return _head; ; } }
            public override Stack<T> Tail { get { return _tail; } }
            public override bool IsEmpty { get { return false; } }

            public Cons(T head, Stack<T> tail)
            {
                _head = head;
                _tail = tail;
            }
        }
    }

    class Program
    {       
        static void Main(string[] args)
        {
            var s = Stack<int>.Empty.Push(1).Push(2).Push(3).Push(4).Push(5);
            while (!s.IsEmpty)
            {
                Console.Write("{0} ", s.Head);
                s = s.Tail;
            }
            Console.ReadKey(true);
        }
    }
}

不可变的AVL树

大多数人对堆栈没有留下深刻印象,因为它不是一个“严肃”的数据结构,但AVL树和其他自平衡数据结构令人印象深刻。树实际上很容易编写为不可变的数据结构。

此版本支持O(log n)插入和查找,但未显示删除,但也可以在O(log n)中实现。换句话说,不可变的AVL树具有与其可变变体相同的计算复杂度:

using System;
using System.Linq;

namespace Juliet
{
    public abstract class AvlTree<T>
        where T : IComparable<T>
    {
        static AvlTree<T> Make(AvlTree<T> left, T value, AvlTree<T> right)
        {
            return new Node(left, value, right, Math.Max(left.Height, right.Height) + 1, left.Count + right.Count + 1);
        }

        public static readonly AvlTree<T> Empty = new Nil();

        protected abstract AvlTree<T> Left { get; }
        protected abstract AvlTree<T> Right { get; }
        protected abstract T Value { get; }
        public abstract int Height { get; }
        public abstract int Count { get; }

        public bool Contains(T v)
        {
            if (this.Height == 0) { return false; }
            else
            {
                int compare = v.CompareTo(this.Value);
                if (compare < 0) { return this.Left.Contains(v); }
                else if (compare > 0) { return this.Right.Contains(v); }
                else { return true; }
            }
        }

        public AvlTree<T> Insert(T v)
        {
            if (this.Height == 0) { return Make(this, v, this); }
            else
            {
                int compare = v.CompareTo(this.Value);
                if (compare < 0) { return Balance(Make(this.Left.Insert(v), this.Value, this.Right)); }
                else if (compare > 0) { return Balance(Make(this.Left, this.Value, this.Right.Insert(v))); }
                else { return this; }
            }
        }

        private static AvlTree<T> Balance(AvlTree<T> tree)
        {
            if (tree.Height > 0)
            {
                int outerBalanceFactor = tree.Left.Height - tree.Right.Height;
                if (outerBalanceFactor <= -2 && tree.Right.Height > 0) // right-side too heavy
                {
                    int innerBalanceFactor = tree.Right.Left.Height - tree.Right.Right.Height;
                    if (innerBalanceFactor <= -1) // right-right case
                    {
                        return LeftRotate(tree);
                    }
                    else if (innerBalanceFactor >= 1) // right-left case
                    {
                        return DoubleLeftRotate(tree);
                    }
                }
                else if (outerBalanceFactor >= 2 && tree.Left.Height > 0) // left-side too heavy
                {
                    int innerBalanceFactor = tree.Left.Left.Height - tree.Left.Right.Height;
                    if (innerBalanceFactor <= -1) // left-left case
                    {
                        return DoubleRightRotate(tree);
                    }
                    else if (innerBalanceFactor >= 1) // left-right case
                    {
                        return RightRotate(tree);
                    }
                }
            }
            return tree;
        }

        private static AvlTree<T> LeftRotate(AvlTree<T> tree)
        {
            if (tree.Height > 0 && tree.Right.Height > 0)
            {
                T p = tree.Value;
                T q = tree.Right.Value;
                AvlTree<T> a = tree.Left;
                AvlTree<T> b = tree.Right.Left;
                AvlTree<T> c = tree.Right.Right;
                return Make(Make(a, p, b), q, c);
            }
            return tree;
        }

        private static AvlTree<T> RightRotate(AvlTree<T> tree)
        {
            if (tree.Height > 0 && tree.Left.Height > 0)
            {
                T q = tree.Value;
                T p = tree.Left.Value;
                AvlTree<T> a = tree.Left.Left;
                AvlTree<T> b = tree.Left.Right;
                AvlTree<T> c = tree.Right;
                return Make(a, p, Make(b, q, c));
            }
            return tree;
        }

        private static AvlTree<T> DoubleLeftRotate(AvlTree<T> tree)
        {
            if (tree.Height > 0)
            {
                // right-rotate right child, left-rotate root
                return LeftRotate(Make(tree.Left, tree.Value, RightRotate(tree.Right)));
            }
            return tree;
        }

        private static AvlTree<T> DoubleRightRotate(AvlTree<T> tree)
        {
            if (tree.Height > 0)
            {
                // left-rotate left child, right-rotate root
                return RightRotate(Make(LeftRotate(tree.Left), tree.Value, tree.Right));
            }
            return tree;
        }


        sealed class Nil : AvlTree<T>
        {
            protected override AvlTree<T> Left { get { throw new Exception("Empty tree"); } }
            protected override AvlTree<T> Right { get { throw new Exception("Empty tree"); } }
            protected override T Value { get { throw new Exception("Empty tree"); } }
            public override int Height { get { return 0; } }
            public override int Count { get { return 0; } }
        }

        sealed class Node : AvlTree<T>
        {
            readonly AvlTree<T> _left;
            readonly AvlTree<T> _right;
            readonly T _value;
            readonly int _height;
            readonly int _count;

            protected override AvlTree<T> Left { get { return _left; } }
            protected override AvlTree<T> Right { get { return _right; } }
            protected override T Value { get { return _value; } }
            public override int Height { get { return _height; } }
            public override int Count { get { return _count; } }

            public Node(AvlTree<T> left, T value, AvlTree<T> right, int height, int count)
            {
                _left = left;
                _right = right;
                _value = value;
                _height = height;
                _count = count;
            }
        }
    }

    class Program
    {       
        static void Main(string[] args)
        {
            var tree = AvlTree<int>.Empty;
            foreach(int i in Enumerable.Range(1, 1000000).OrderBy(_ => Guid.NewGuid()))
            {
                tree = tree.Insert(i);
            }

            Console.Write("Tree height: {0}, tree count: {1}", tree.Height, tree.Count);

            Console.ReadKey(true);
        }
    }
}

答案 8 :(得分:1)

坦率地说,我认为你不能。函数式编程(如'良好'OO编程,与'类程序'相比)需要心智模型转换。如果没有花时间并使用它,你就无法将其理解为程序编码器。

如果你真的想要某人理解函数式编程,那就给他们一份Little Schemer或其他同等书,让他们坐下来练习练习。没有什么能帮助他们建立必要的心理模型。

答案 9 :(得分:1)

我们将这个教程用于Haskell,它运行得非常好,它很轻松,但做了很好的解释:

Learn you a Haskell for Great Good

答案 10 :(得分:0)

将其与句子结构进行比较。

我击球,接球,丢球: 程序
击(球)
赶上(球)
扔(球)

OO
Ball.Hit()
Ball.Catch()
Ball.Throw()

答案 11 :(得分:0)

新答案。这适用于非编程高级用户和OO程序员。

指出Excel正常运作。

电子表格中的每个单元格都是一个变量,其中包含一个声明其他单元格/变量之间关系的函数。