Уважаемые коллеги по Хабра! Наконец-то я приступаю к изложению идей функционального программирования, а также основ языка F#.
Сегодня нам нужно будет сделать самое главное — понять основные принципы функционального программирования и проникнуться его духом.
Заранее прошу прощения у тех функциональных гуру, которые ждут более содержательных уроков — но я хотел начать с самого начала.
Соответственно, для начала история из жизни:
Когда я был молодым и преподавал программирование на первом курсе факультета прикладной математики МАИ, один из студентов не мог понять, что означает X:=X+1. «Как это возможно, что X может быть равно X+1Э» Пришлось ему объяснять, как это возможно, и в этот момент в нем умер функциональный программист.Почему? Давайте разберемся.
Название «функциональное программирование» происходит от слова «функция».
Функциональная программа строится из функций.
Собственно, сама программа — это тоже функция, которую можно применить к входным данным и получить результат. "Ну и что?" - ты говоришь.
– Программа на языке Паскаль также состоит (или может состоять) из функций.
Правильно, только в программе на Паскале помимо функций есть еще операторы , управляющие структуры, такие как циклы, и, самое главное, оператор присваивания .
Программа на языке Паскаль определяет набор шагов, каждый из которых читы некоторое состояние памяти компьютера путем оценки выражений и присвоения их переменным.
В функциональном программировании это не так.
Здесь только функции, а все данные неизменный (неизменный).
Вся программа построена из комбинации функций, которые работают с данными, преобразуя одно значение в другое.
Давайте рассмотрим пример: функция расчета факториал .
В императивном языке, таком как C#, функция будет выглядеть так:
public int fact(int n)
{
int f = 1;
for (int i = 2; i <= n; i++)
{
f = f * i;
}
return f;
}
Запускаем аккумулятор и в цикле последовательно умножаем его на следующее натуральное число.
В случае с F# у нас нет понятия переменной, и нам приходится строить определение только с помощью комбинации функций:
let rec fact n =
if n = 1 then 1
else n*fact(n-1)
in fact 5;;
Это выражение вычисляет факториал 5. Основное вычисляемое выражение здесь: факт 5 , и все написанное до него определяет значение символа факт .
В общем, смысл дизайна позволять – дать синонимичное название какому-либо выражению.
Например,
let x = 1 in
let y = x+2 in
x+5*y;;
Здесь имена x и y определяются только в контексте выражения, расположенного после слова in – поэтому такое выражение полностью аналогично выражению 1+5*(1+2).
В нашем случае после позволять происходит еще кое-что запись Показать, что определение функции рекурсивно.
Сама функция определяется как чистая композиция других функций: условная функция (действительно, в этом случае оператор if можно было бы заменить функцией if с тремя аргументами, как это сделано, например, в Excel), умножение, вычитание и сама функция факта.
На самом деле можно переписать определение так (здесь я для ясности опускаю некоторые моменты, связанные с порядком вычисления условного выражения):
let rec fact n = iff ((=) n 1) 1 ((*) n (fact ((-) n 1)));;
Из этой записи видно, что кроме композиции функций здесь нет ничего.
На самом деле в чистом функциональном программировании всего две операции: аппликация (применение функции к аргументу) и абстракция (умение конструировать функцию из выражения).
Последовательное написание нескольких функций вида f g u v означает ((f g) u) v, т.е.
применение осуществляется слева направо, сначала f применяется к g, в результате должна получиться функция, которая применяется к u, и так на.
Абстракция позволяет получить функцию из выражения, другими словами, написать функциональная константа .
Например, запись весело х -> х*2 означает функцию, которая при наличии целочисленного аргумента возвращает его двойное значение.
Рассмотрим еще одно важное понятие – карри .
Все функции в функциональном программировании являются одиночными функциями, т.е.
имеют один аргумент. Интересно, а что в этом случае делать с такими операциями, как сложение? Есть два варианта.
Мы можем думать о сложении как о функции упорядоченная пара , Например
let plus' (x,y) = x+y;;
(при этом внимательный читатель заметил, что в языке конструкция упорядоченной пары, тройки и т.п.
является полноценным типом данных), но можно поступить в традициях функционального программирования, описав функцию плюс как этот:
let plus x y = x+y;;
Если в первом случае тип функции целое*целое -> целое (т.е.
отображение пары на целочисленный тип), то во втором - интервал -> (интервал -> интервал) , т.е.
это будет функция целочисленного типа, которая возвращает функцию из целого числа в целое число.
Например, выражение (плюс 1) будет означать функцию приращения, т.е.
увеличение аргумента на 1. Запись плюс 1 2 будет трактоваться как (плюс 1) 2, т.е.
сначала мы получим функцию приращения, а потом ее применим на цифру 2, получив требуемый результат. Функция «плюс» называется каррированной функцией сложения, и в функциональном программировании обычно используются именно такие функции.
В частности, все стандартные операции можно использовать в каррированной форме, заключая операцию в круглые скобки и используя префиксную запись, например:
(+) 1 2;;
let incr = (+)1;;
Итак, мы поняли следующие основные принципы функционального программирования: все есть функция , ничего не существует, кроме функции , нет оператора присваивания или переменных .
Давайте поймем еще один принцип: функции являются полноценными сущностями (первосортные граждане), вы можете передавать функции в качестве параметров, описывать функциональные константы и работать с функциями.
Например:
let double x = x*2.0;;
описывает функцию умножения на 2, примерно так:
public double doub(double x)
{
return x * 2.0;
}
Что приятно, в F# практически никогда нет необходимости указывать типы параметров и значений — они получаются автоматически благодаря системе вывода типов.
В частности, если мы введем приведенное выше определение двойной функции в интерпретатор F #, мы получим: val double: float -> float Это означает, что тип был автоматически выведен как функция от числа с плавающей запятой.
Рассмотрим следующее выражение:
let double x = x*2.0 in
let quad = (double >> double) in quad 5.0;;
Здесь мы сначала описываем двойную функцию, затем еще одну четверную функцию, которая представляет собой композицию двух двойных функций, обозначаемых > > .
В заключение этого урока я хотел бы предложить вам несколько задач и вопросов для размышления в качестве домашнего задания.
Домашнее задание:
- Определите функцию для поиска корней квадратного уравнения.
- Реализуйте функциональную программу для нахождения суммы чисел от 1 до 100. Суммы квадратов этих чисел.
- Какая из функций факториала в этом руководстве, по вашему мнению, «лучше» — одна, реализованная на C#, или одна, реализованная на F#?
- Опишите функцию каррирования, которая по заданной двухместной некаррированной функции (то есть функции пары значений) возвращает соответствующую функцию в каррированной форме.
- Опишите несущую функцию, противоположную той, что требовалась на шаге 4.
-
Внешние Ит-Ресурсы – Не Могу (,) Отказаться
19 Oct, 24 -
Шаблон Zabbix Для Мониторинга Репликации Dfs
19 Oct, 24 -
Api Поиска Google Ajax
19 Oct, 24 -
Перспективы И Проблемы Платформы Android
19 Oct, 24 -
X-Wing Развалился В Воздухе
19 Oct, 24 -
Игровые Сообщества — Часть Нашего Будущего
19 Oct, 24