Статьи

Введение в функциональные языки: часть вторая

Ранее мы давали введение в функциональные языки . Теперь рассмотрим примеры функционального программирования с использованием F #.

F # — это язык, разработанный в Microsoft Research и выпущенный для широкой публики. Он считается OCaml в .Net, поскольку имеет тесные связи с OCaml. Что делает его значимым для меня, так это его инструментальная поддержка. В дополнение к семейству языков .Net он имеет фантастическую языковую поддержку с Visual Studio. Это редко случается с другими функциональными языками и особенно с интересующими меня языками (Clojure, Scala, Erlang). F # также является гибридным языком. Он имеет объектно-ориентированную поддержку и способен относительно легко интегрироваться в C # или другой язык CLR. В настоящее время F # является языком со строгой типизацией, который использует интерференцию типов и работает на CLR. Я еще не испытывал это, но все признаки указывают, что это может быть запущено на Моно.

/// A very simple constant integer
let int1 = 1
/// A function on integers
let f x = 2*x*x - 5*x + 3
// A simple tuple of integers
let pointA = (1, 2, 3)
// A simple tuple of an integer, a string and a floating point number
let dataB = (1, "fred", 3.1415)

Листинг 1: примеры F # let из Tutorials.fs

Первый важный оператор в F # — let. При работе с переменными, как в int1, важно понимать, что это на самом деле не императивная переменная, а символическое присваивание. let также может назначать переменные для функций, как в примере с функцией f. Последние два примера кода являются назначениями кортежей. pointA — это последовательность всех типов, а dataB — последовательность нескольких типов. В листинге 2 показаны некоторые списки и введены некоторые новые операторы в F #.

Последовательности в F #

let listA = [ ]  			/// The empty list
let listB = [ 1; 2; 3 ] /// A list with 3 integers
let listC = 1 :: [2; 3] /// A list with 3 integers, note :: is the 'cons' operation
let listD = [5 .. 10] /// A list of number 15 through 10
let listE = listC @ listD /// A concatenated list of listC and listD

/// Compute the sum of a list of integers using a recursive function
let rec SumList xs =
match xs with
| [] -> 0
| h::t -> h + SumList t
/// Sum of a list
let sum = SumList [1; 2; 3]

Листинг 2: Примеры списка F # 

Следующим важным элементом F # является работа со списками. Код назначения списка в листинге 2 легко читается с двумя исключениями: операторы :: и @. Оператор :: является оператором cons — это еще один способ описания списка. Это замечательно, когда используется в сопоставлении с образцом, который мы увидим позже. Оператор @ объединяет два списка.

Функция SumList является рекурсивной функцией (определяется ключевым словом rec). Это занимает последовательность; если последовательность пуста (определяется []), возвращается 0, в противном случае заголовок последовательности (h) добавляется к рекурсивному вызову SumList оставшейся последовательности. В F # очень распространено видеть соответствие для последовательности с head или h как переменной, представляющей head и tail или t, представляющей остаток последовательности.

Работа с коллекцией может быть совершенно другой, используя функциональные методы. Например, в Java подход состоит в том, чтобы перебрать коллекцию, возможно, с циклом «для каждого». В Groovy мы использовали бы каждый метод, передаваемый в замыкании. В F # есть модуль List, и можно передать функции модуля List последовательность. Функция будет применяться к каждому элементу последовательности. Обратите внимание на абстракцию от итераций и ветвления.

let Square x = x*x               /// A function that squares its input
let squares1 = List.map Square [1; 2; 3; 4] // Map a function across a list of values

Листинг 3: Функция отображения списка F # 

Функция map модуля List возвращает новый список (помните, что списки неизменны), с тем же количеством элементов, каждый элемент является квадратом исходного списка.

веселье в F #

Еще одна особенность языка F # — это забавное ключевое слово. Ключевое слово fun позволяет нам писать анонимную функцию на лету. Это позволило бы нам переписать функции из листинга 3 в листинг 4.

let squares2 = List.map (fun x -> x*x) [1; 2; 3; 4]

Листинг 4: ключевое слово F # fun определяет функцию на лету

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

Трубный оператор в F #

Для более длинных функций с карри иногда трудно прочитать, что происходит, или порядок выполнения. В F # вводится оператор, который делает это очень простым, он называется оператором канала (|>). Листинг 5 переписывает функции из листинга 3 и 4 еще раз, используя оператор pipe.

let squares3 = [1; 2; 3; 4] |> List.map (fun x -> x*x) 

Листинг 5: оператор F # pipe

На этот раз последовательность передается в List.map. Таким образом, эти операторы труб часто объединяются в цепочку, как показано в листинге 6.

let SumOfSquaresUpTo n = 
[1..n]
|> List.map Square
|> List.sum

Листинг 6: F # оператор канала

Дискриминационные союзы в F #

Еще одна особенность F # — возможность создавать различимое объединение, тип которого ограничен определенным набором типов. Например:

type exampleUnion =
| ex1 of int * string
| ex2 of bool
| ex3
// constructing instances
let a = ex1(42,"life")
let b = ex3
let f du =
// pattern matching
match du with
| ex1(x,s) -> printfn "x is %d, s is %s" x s
| ex2(b) -> printfn "b is %A" b
| ex3 -> printfn "three"

f a // x is 42, s is life
f b // three

Листинг 7: Дискриминационный союз F #

В листинге 7 определяется тип, который ограничен парой целочисленных строк, логическим значением или нулем. Это может быть невероятно полезно, если использовать сопоставление с шаблоном, как видно из этого списка. Это отличный пример техники, которую нельзя сделать с помощью оператора switch. Там, где оператор switch должен относиться к определенному типу, сопоставление с образцом в F # способно включать тип и обеспечивать легкий доступ к полезной нагрузке этих типов.

Поскольку мы сопоставляем шаблон, давайте закончим реализацией последовательности Фибоначчи, если F #.

let rec fib n =
match n with
| 1 -> n
| 2 -> 1
| n -> fib (n-1) + fib (n-2)

Листинг 8: Функция Фибоначчи F #

Этот пример F # не такой краткий, как пример Mathematica в листинге 6, но близкий и очень легко читаемый. В отличие от кода Mathematica, F # работает на CLR и его достаточно легко интегрировать в приложение C #.

Резюме

Это был вихревой тур по концепции функционального программирования. Есть целые книги, написанные на тему функционального программирования и F #, которые не полностью охватывают эту тему. В этой статье рассматриваются некоторые концептуальные различия функционального развития, с которыми сталкивается человек, работавший исключительно с императивными языками. Понятия лямбда-выражений, карри, замыкания и кортежи были раскрыты достаточно подробно, чтобы начать. Были приведены примеры F #, чтобы применить некоторые из определенных терминов на практике.

Потребность в функциональном программировании растет, и барьер для входа никогда не был меньше, пришло время добавить функциональное программирование в ваш портфель знаний. Я бы порекомендовал взглянуть на Scala или Clojure на JVM или F # на CLR.

Ресурсы

Web

  • http://en.wikipedia.org/wiki/Functional_programming 
  • http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/default.aspx
  • http://msdn.microsoft.com/en-us/fsharp/default.aspx 
  • http://deepfriedbytes.com/podcast/episode-23-functional-programming-in-csharp-with-oliver-sturm/ 
  • http://www.strangelights.com/fsharp/wiki/

книги

  • Программирование Scala от Venkat Subramaniam
  • Программирование Clojure от Стюарта Хэллоуэя
  • F # в двух словах Аманда Лаучер и Тед Ньюард
  • Эксперт F # от Дона Сайма
  • Изучение F # 1.0 Крисом Смитом.
  • Функциональное программирование в C # Оливер Штурм