Влюбленность В F#: Доза 1: Дух Функционального Программирования

Уважаемые коллеги по Хабра! Наконец-то я приступаю к изложению идей функционального программирования, а также основ языка 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.
Теги: #fsharp #функциональное программирование #уроки программирования #microsoft #программирование #F#
Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.