F Sharp
F# | |
---|---|
Класс языка | мультипарадигменный: функциональное, объектно-ориентированное, обобщённое, императивное программирование |
Появился в | 2005 |
Автор | Microsoft Research |
Разработчик | Майкрософт и F Sharp Software Foundation[вд] |
Расширение файлов | .fs , .fsi , .fsx или .fsscript |
Выпуск | 8.0 (14 ноября 2023) |
Система типов | строгая, статическая / динамическая |
Испытал влияние | Objective Caml, C#, Haskell |
Лицензия | Apache Software License |
Сайт | fsharp.org |
ОС | Кроссплатформенное программное обеспечение (.NET Framework, Mono) |
Медиафайлы на Викискладе |
F# (произносится эф-шарп) — мультипарадигмальный язык программирования из семейства языков .NET, поддерживающий функциональное программирование в дополнение к императивному (процедурному) и объектно-ориентированному программированию. Структура F# во многом схожа со структурой OCaml с той лишь разницей, что F# реализован поверх библиотек и среды исполнения .NET. Язык был разработан Доном Саймом (англ. Don Syme) в Microsoft Research в Кембридже, в настоящее время его разработку ведёт Microsoft Developer Division. F# достаточно тесно интегрируется со средой разработки Visual Studio и включён в поставку Visual Studio 2010/2012/2013/2015/2017/2019/2022; разработаны также компиляторы для Mac и Linux[1].
Microsoft интегрировала среду разработки F# в Visual Studio 2010 и более новые версии.
4 ноября 2010 года код компилятора F# и основных библиотек к нему опубликован под Apache License 2.0[2].
Особенности
Код на языке F# является безопасным в отношении типов, часто бывает более компактным, чем аналогичный код C#, за счёт вывода типов. В F# действует строгая типизация, неявные преобразования типов полностью отсутствуют, что полностью исключает ошибки, связанные с приведением типов.
Такие возможности, как обобщённое программирование и функции высших порядков позволяют писать абстрактные обобщённые алгоритмы, которые управляют параметризованными структурами данных (например, массивами, списками, графами, деревьями).
Во многих языках большинство значений являются переменными. Например, в результате исполнения следующего кода на языке C++ в переменной x
будет храниться значение 3:
int x = 2;
x++;
В F#, напротив, по умолчанию все значения являются константами. F# допускает переменные, для чего требуется специально помечать значения как изменяемые при помощи слова mutable:
let x = 2 // неизменяемое значение
let mutable y = 2 // переменная
x <- 3 // ошибка
y <- 3 // Ok. y = 3
Также в F# есть ссылочные типы и объекты, которые также могут содержать изменяемые значения. Тем не менее, бо́льшая часть кода является чистыми функциями, что позволяет избежать многих ошибок и упростить отладку. Кроме того, упрощается распараллеливание программ. При всём этом код редко становится сложнее, чем аналогичный код на императивном языке.
Одна из основных идей F# заключается в том, чтобы удостовериться, что имеющийся код и типы в функциональном языке программирования могут быть легко доступны из других .NET-языков. Программы на F# компилируются в сборки CLR (файлы с расширениями .exe и .dll), однако, для их запуска необходима установка пакета среды исполнения дополнительно к .NET Framework.
Интересной особенностью (и отличием от OCaml) является управление логической вложенностью конструкций кода за счёт отступов в виде произвольного количества пробелов (и только лишь пробелов). Знаки табуляции для этой цели не поддерживаются. Это приводит к постоянным дискуссиям на форумах опытных разработчиков, которые привыкли пользоваться знаками табуляции в других языках программирования.
Компилятор и интерпретатор
F# — компилируемый язык программирования, при этом в качестве промежуточного языка используется язык Common Intermediate Language (CIL), так же как и в программах, написанных на языках C# или VB.NET.
Наряду с F#-компилятором (fsc) присутствует и F#-интерпретатор (fsi), который исполняет F#-код интерактивно.
Отличительной чертой F#-компилятора и F#-интерпретатора является возможность воспринимать код двумя разными способами — немедленно (по умолчанию) и отложенно (программисту требуется явно указать это в исходном коде). В случае немедленной интерпретации, выражения вычисляются заранее в момент запуска программы на выполнение, независимо от того, вызываются ли они в процессе выполнения программы или нет. В этом случае, зачастую снижается производительность выполнения программы, а также происходит неэкономное расходование ресурсов системы (например, памяти). В случае ленивой интерпретации кода, выражения вычисляются только в тот момент, когда к ним происходит непосредственное обращение в процессе выполнения программы. Это избавляет программу от перечисленных выше недостатков, но снижает предсказуемость в плане объёма и последовательности использования ресурсов (процессорного времени, памяти, устройств ввода-вывода и т. п.) на различных этапах выполнения программы.
Примеры
Синтаксис F# построен на математической нотации, а программирование чем-то похоже на алгебру, что делает F# похожим на Haskell. Например, когда вы определяете новый тип, то можете указать, что переменными этого типа будут «целые или строки». Вот как это выглядит:
type myType = IntVal of int | StringVal of string
Важным примером таких типов является Option, который содержит либо значение некоторого типа, либо ничего.
type Option<a> = None | Some of a
Он является стандартным типом F# и часто используется в ситуациях, когда результатом работы какого-то кода (например, поиска в структуре данных) является значение, которое может и не быть получено.
Код также представляет собой математическую нотацию. Следующая конструкция эквивалентна f(x) = x + 1 в алгебре:
let f x = x + 1
F# работает следующим образом: тип «f
» представляет собой «int -> int
», то есть функция получает на вход целое и выдаёт на выход целое.
F# позволяет получить доступ абсолютно ко всему, что есть в FCL. Синтаксис для работы с библиотеками .NET в этом смысле максимально близок к синтаксису C#. Особенности языка заметны при использовании всего спектра возможностей F#. К примеру, следующий код применяет функцию к элементам списка:
let rec map func lst =
match lst with
| [] -> []
| head :: tail -> func head :: map func tail
let myList = [1;3;5]
let newList = map (fun x -> x + 1) myList
В «newList
» теперь находится «[2;4;6]
».
Разбор списка в этой функции ведётся с помощью ещё одной мощной возможности сопоставления с образцом. Она позволяет задавать образцы при совпадении с которыми вычисляются соответствующие вхождения оператора match. Первый образец «[]» означает пустой список. Второй — список состоящий из первого элемента и хвоста (который может быть произвольным списком, в том числе и пустым). Во втором образце значение головы связывается с переменной head, а хвоста с tail (имена могут быть произвольные). Таким образом кроме основной задачи образец ещё позволяет производить декомпозицию сложных структур данных. Например, в случае с типом Option сопоставление с образцом выглядит так:
match x with
| Some v -> printfn "Найдено значение %d." v
| None -> printfn "Ничего не найдено."
| None -> printfn "Привет"
Язык поддерживает генераторные выражения, определенные для множеств { … }
, списков [ … ]
и массивов [| … |]
Например:
let test n = [ for i in 0 .. n do
if i % 2 = 0 then
yield i ]
Функция map является одной из стандартных функций над списками, которые содержатся в модуле List. Также существуют функции для других структур данных, объявленные в модулях Array, Set, Option.
Полезным инструментом является оператор pipe-forward (|>), который позволяет писать цепочки вызовов функций в обратном порядке. В результате имеет место такой код (в комментариях указаны промежуточные значения):
[1; 2; 5]
|> List.map ((+) 1) // [2; 3; 6]
|> List.filter (fun x -> x % 2 = 0) // [2; 6]
|> List.sum // 8
Использование оператора |>
исключает необходимость использования большого числа скобок, а также изменяет визуальное восприятие кода. И теперь данный код читается так: взять такой-то список, прибавить к каждому элементу 1, затем оставить только четные элементы, вернуть их сумму. То есть, описывается последовательность действий, выполняемая над изначальным объектом, в том порядке, в котором она происходит и на компьютере.
Далее небольшая демонстрация того, насколько функции .NET расширяют возможности F#. Одним из примеров являются оконные приложения и событийная обработка. Событийная обработка означает — какие-то действия в программе происходят только как реакция на определенные события — действия пользователей, подключение устройств и т. д. Проект можно создать как в Visual Studio, так и в любом текстовом документе, который затем подается на вход компилятору F# (fsc).
// open - подключение модулей и пространств имен для использования содержащихся в них
// значений, классов и других модулей.
open System.Windows.Forms // - классы Form (окно), Button (кнопка)и т. д.
// Beep - звуковой сигнал
// В качестве аргументов beep передаются еще некоторые параметры, которые мы не используем
let beep _ = System.Console.Beep()
// создание окна с программным именем окно !необходимо вызывать слово-функцию отображения - к примеру Application.Run(окно)!
// Visible - булевское значение, является ли окно видимым
// TopMost - отображается ли окно на переднем плане (очерёдность окон с одинаковым значением в обратном порядке вызова)
// Text - текст заголовка окна
let window = new Form(Visible=true,TopMost=true,Text="",
Top = 0, Left = 0, Height = 512, Width = 768)
window.WindowState <- FormWindowState.Normal // Нормальное (, Свёрнутое, Развёрнутое) окно. Просто для примера не внесено в конструктор
window.ClientSizeChanged.Add beep
window.KeyDown.Add beep
window.KeyPress.Add beep
window.KeyUp.Add beep
Application.Run window // отображение окна
Факториал
Рекурсивная функция вычисления факториала нативным способом:
let rec fac n =
if n < 2 then 1
else n * fac(n - 1)
Рекурсивная функция вычисления факториала, оптимизированная под хвостовую рекурсию.
let factorial num =
let rec fac num acc =
match num with
|x when x < 2 -> acc
|_ -> fac (num - 1) (acc * num)
fac num 1
Функция вычисления факториала, в императивном стиле с использованием изменяемого состояния.
let factorial num =
if num < 2 then 1
else
let mutable fac = 1
for i in [2..num] do
fac <- fac * i
fac
Функция вычисления факториала с использованием свертки списка и каррированой операции умножения:
let fac n = List.fold (*) 1 [1..n]
Рекурсивная функция вычисления чисел Фибоначчи с использованием метода сопоставления с образцом:
let rec fib n a b =
match n with
| 0 -> a
| 1 -> b
| _ -> fib (n - 1) b (a + b)
Примечания
- ↑ Ссылки для загрузки F# на сайте Microsoft Research . Дата обращения: 24 июня 2011. Архивировано 24 июня 2011 года.
- ↑ Announcing the F# Compiler + Library Source Code Drop . Дата обращения: 5 ноября 2010. Архивировано 17 января 2013 года.
См. также
Ссылки
- F# (англ.) — Microsoft F# Developer Center.
- Введение в F#. Евгений Лазин, Максим Моисеев, Давид Сорокин, «Практика функционального программирования», 2010, № 5
- Введение в F# — интервью с Доном Саймом.
- Обсуждение книги по F# на русском языке Дона Сайма «The Definitive Guide to F#»
- Три парадигмы F# — Хабрахабр.
- Try F# Tutorials — Ознакомление с языком F#. программирования
Литература
- Крис Смит (Smith, Chris). Программирование на F# = Programming F#. — O'Reilly, 2011. — 448 с. — ISBN 978-5-93286-199-8.
- Дмитрий Сошников. Функциональное программирование на F#. — Москва: ДМК Пресс, 2011. — 192 с. — ISBN 978-5-94074-689-8.
- Syme, Don; Granicz, Adam; Cisternino, Antonio. Expert F#. — Apress, 2007. (англ.)