Подтвердить что ты не робот

Объяснение функционального программирования объектно-ориентированным программистам и менее техническим людям

Каковы некоторые хорошие примеры, которые я могу использовать для объяснения функционального программирования?

Аудитория будет людьми с небольшим опытом программирования или людьми, у которых есть только объектно-ориентированный опыт.

4b9b3361

Ответ 1

Аудитория будет людьми с мало опыта программирования,

Действительно ли можно объяснить функциональное программирование, позволить ОО или процедурной или любой парадигме программирования людям без большого опыта программирования?

или людей, которые имеют объектно-ориентированный опыт.

Вероятно, наилучшим примером было бы преобразование известных шаблонов проектирования в их функциональный эквивалент. Возьмем канонический пример преобразования списка 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
        }
    }
}

Хорошо, теперь у нас есть одна и та же программа в гораздо меньшем количестве строк кода, ее читаемость и ее очень очевидное, что она делает.

Теперь кто-то может сказать: "Это аккуратный трюк, но когда я его использую" - есть много случаев, когда вы хотите передавать такие функции, как это. Это дает вам гораздо больше возможностей для абстрактного управления потоками ваших программ по-новому. Адаптированный из приведенного примера здесь, рассмотрим класс, который обрабатывает файлы:

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

Высокий.

Ответ 2

Вначале легче описать функциональное программирование по его основным характеристикам:

  • Функции (очевидно)
  • Нет побочных эффектов
  • Неизменность
  • Рекурсия очень важна.
  • Очень Параллелизуемый

Из них наиболее важной характеристикой является понятие о каких-либо побочных эффектах, поскольку из этого следуют другие характеристики.

Функциональное программирование используется в местах, которые вы не ожидаете. Например, он используется для запуска светофоров и коммутаторов (например, коммутаторы Ericsson работают с Erlang).

Ответ 3

Те, у кого мало опыта в программировании, вероятно, проще, так как у них не будет столько концепций программирования, которые бы мешали: функциональное программирование очень похоже на математические функции как выражения, поэтому покажите им математику.

Для толпы OO вы можете показать, как закрытие подобно инкапсуляции, и, таким образом, как функциональное программирование охватывает ООП. Вы также можете показать, насколько полезны функции более высокого порядка, в частности, как они вызывают некоторые шаблоны ООП (такие как шаблоны стратегий и шаблонов, а также шаблон посетителя на языках с помощью нескольких методов) исчезают на языке (Питер Норвиг утверждает, что 16 из 23 шаблоны GOF проще в Dylan и Lisp). В качестве контрагента OOP для передачи сообщений (а не CLOS-подход) представляет собой шаблон в FP, который невидим в ООП.

Продемонстрируйте, как легко писать общие арифметические выражения, а также состав и дифференциацию функций, как конструкцию программирования, и у вас есть оба из них.

Вы можете или не хотите показывать многопоточности CLOS для людей OO; они будут либо беспорядками, либо гикмазмом. В любом случае, это может быть беспорядочно.

Некоторые другие возможности показать, насколько гибкий FP: ленивая оценка может быть реализована с использованием нулевых анонимных функций. FP может поддерживать как ООП на основе классов, так и прототипов. Существует множество других примеров (например, стиль продолжения прохождения), но они обычно требуют знания FP до обучения.

SICP есть много интересных примеров и проблем; он должен стать вдохновляющим.

Ответ 4

Попробуйте найти эту статью под названием Функциональное программирование для остальной части U, ниже - выдержка.

В этой статье я расскажу о наиболее широко используемых идеях с функциональных языков, используя примеры, написанные на Java (да, вы могли бы писать функциональные программы на Java, если бы вы чувствовали себя особенно мазохистскими). В следующих параграфах мы возьмем Java как есть и внесем в него изменения, чтобы превратить его в полезный функциональный язык. Пусть начнутся наши поиски.

Ответ 5

OO относится к существительным, а функциональное программирование - о глаголах. Прочтите этот неплохой блог по этому вопросу Стив Йегге.

Ответ 6

Почему вопросы функционального программирования у Джона Хьюза есть несколько хороших примеров, но даже лучше, это объясняет почему функциональных языков программирования, а не только как. Вы можете сделать гораздо хуже, чем создать презентацию на основе этой статьи!

Ответ 7

Функциональное программирование для объектно-ориентированного программиста Брайана Марика. Я не связан или что-то еще, только что пришел прямо сейчас:

https://leanpub.com/fp-oo

Дядя Боб любит это!:)

Ответ 8

Непрерывные структуры данных

Вы знаете, я уже прокомментировал людям о функциональном программировании и создал всю "неизменную структуру данных". Это обычно приводит к уму людей - как вы должны что-то добавлять в коллекцию, если вы не можете ее добавить?

Непосредственный ответ: "ну, нет, вы просто создаете новую версию своей структуры данных с добавленным объектом", а немедленный ответ "не очень эффективен". Ну, это так.

Неизменяемый стек

Каноническая неизменяемая структура данных - неизменяемый стек, который в основном представляет собой node, содержащий два значения readonly: голова и хвост. Нажатие и выскакивание из стека - 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);
        }
    }
}

Ответ 9

Я не думаю, что вы можете, честно говоря. Функциональное программирование (например, "хорошее" программирование OO, "процедурное с классами" ) требует изменения ментальной модели. Вы просто не можете понять это как процедурный кодер, не потратив время и не используя его.

Если вы действительно хотите, чтобы кто-то понял функциональное программирование, получите им копию Little Schemer или какой-либо другой эквивалентной книги и попросите их сесть и выполнить упражнения. Ничто другое не поможет им построить необходимую ментальную модель.

Ответ 11

Сравните это с структурой предложения.

Я попал в мяч, поймал мяч, бросил мяч:
Процедурный
Хит (мяч)
Поймайте (шар)
Бросьте (шар)

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

Ответ 12

Новый ответ. Это будет работать как для не программирующих пользователей, так и для программистов OO.

Укажите, что Excel работает.

Каждая ячейка в электронной таблице является переменной, которая содержит функцию, которая объявляет связь между другими ячейками/переменными.