F#  — багатопарадигмальна мова програмування, розроблена в підрозділі Microsoft Research і призначена для виконання на платформі .NET. Вона поєднує в собі виразність функційних мов, таких як OCaml і Haskell, з можливостями і об'єктною моделлю .NET.

Короткий огляд F# Функційна мова максимально адаптована до використання в .NET , відповідно, вона не заперечує і імперативного підходу.

Протягом тривалого часу F# існував як дослідницький проект, основним завданням якого було збагатити імперативну мову C# можливостями традиційно доступними лише функціональним мовам. Безліччю нововведень C# 3.0 з Visual Studio 2008 завдячує саме йому. Сам по собі F# не створений з чистого аркуша в Microsoft, в його основу покладено досить популярний OCaml, який, у свою чергу, походить від одної з перших типізованих функціональних мов ML. Попри те, що синтаксично F# і OCaml досить близькі, вони не еквівалентні: грубо кажучи, перший становить собою підмножину другого, доповнену доступом до властивостей .NET Framework. Однак деякі програми на OCaml можуть бути практично без модифікацій скомпільовані F#, зворотна компіляція також можлива, зрозуміло, за відсутності звернень до класів .NET Framework.

Історія

Історія F# почалася в 2002 році, коли команда розробників з Microsoft Research під керівництвом Don Syme вирішила, що мови сімейства ML цілком підходять для реалізації функціональної парадигми на платформі .NET. Ідея розробки нової мови з'явилася під час роботи над Generic'ами — реалізацією узагальненого програмування для Common Language Runtime. Відомо, що у свій час як прототип нової мови розглядалася Haskell, але через функціональну чистоту і складнішу систему типів потенційний Haskell .NET не міг би надати розробникам простого механізму роботи з бібліотекою класів .NET Framework, а значить, не давав би якихось додаткових переваг. Як би там не було, за основу був узятий OCaml, мова з сімейства ML, яка не є чисто функціональною і надає можливості для імперативного і об'єктно-орієнтованого програмування. Haskell, хоч і не став безпосередньо батьком нової мови, тим не менше, справив на нього деякий вплив. Наприклад, концепція обчислювальних виразів (computation expressions або workflows), що грають важливу роль для асинхронного програмування та реалізації DSL на F#, запозичена з монад Haskell.

Наступним кроком у розвитку нової мови стала поява в 2005 році її першої версії. З тих пір навколо F# стало формуватися співтовариство. За рахунок підтримки функціональної парадигми мова виявилася потрібною в науковій сфері та фінансових організаціях. Багато в чому завдяки цьому Microsoft вирішила перевести F# зі статусу дослідницьких проектів у статус підтримуваних продуктів і поставити її в один ряд з основними мовами платформи .NET. І це попри те, що останнім часом все більшу активність проявляють динамічні мови, підтримка яких також присутня в .NET Framework. 12 квітня 2010 світ побачила нова версія флагманського продукту для розробників — Microsoft Visual Studio 2010, яка підтримує розробку на F# прямо з коробки.

Основні можливості мови F# є багатопарадигмальною мовою, а це означає, що на ній можна реалізовувати функції вищих порядків, усередині яких виконувати імперативний код і обгортати все це в класи для використання клієнтами, написаними іншими мовами на платформі .NET.

F# функціональна

F#, будучи спадкоємцем традицій сімейства мов ML, надає повний набір інструментів функціонального програмування: тут є алгебричні типи даних і функції вищого порядку, можливість композиції функцій і незмінні структури даних, а також часткове застосування на пару з карруванням. Зі слів експертів в OCaml, в F# не вистачає функціональних об'єктів.

Усі функціональні можливості F # реалізовані поверх загальної системи типів .NET Framework. Однак цей факт не забезпечує зручності використання таких конструкцій з інших мов платформи. При розробці власних бібліотек на F# слід передбачити створення об'єктно-орієнтованих обгорток, які буде простіше використовувати з C# або Visual Basic. NET.

F# імперативна У випадках, коли багатих функціональних можливостей не вистачає, F # надає розробникові можливість використовувати в коді принади змінюваного стану. Це як безпосередньо змінювані змінні, підтримка полів і властивостей об'єктів стандартної бібліотеки, так і явні цикли, а також змінні колекції і типи даних.

F# об'єктно-орієнтована Об'єктно-орієнтовані можливості F #, як і багато чого іншого в цій мові, обумовлені необхідністю надати розробникам можливість використовувати стандартну бібліотеку класів .NET Framework. З поставленим завданням мова цілком справляється: можна як використовувати бібліотеки класів, реалізовані на інших .NET мовах, так і розробляти свої власні. Слід зазначити, однак, що деякі можливості ГО мов реалізовані не самим звичним чином.

Таке змішання парадигм, з одного боку, може привести до плачевних результатів, а з іншого — надає більше гнучкості і дозволяє писати простіший код. Так, наприклад, всередині стандартної бібліотеки F# деякі функції написані в імперативному стилі з метою оптимізації швидкості виконання.

Томаш Петрічек у своєму блозі згадує також про «мовно-орієнтоване» програмування. F# відмінно підходить як для написання вбудованих DSL, тобто імітації предметної області засобами мови програмування, так і для перетворення F# коду в конструкції, виконувані іншими засобами, наприклад в SQL-вирази або в послідовності інструкцій GPU. Крім того, в комплект постачання F# входять утиліти FsYacc і FsLex, які є аналогами таких же утиліт для OCaml, і які дозволяють генерувати синтаксичні та лексичні аналізатори, а значить на F# цілком можна розробити свою власну мову програмування.

Розширені можливості Обчислювальні вирази

Серед нововведень F# можна особливо виділити так звані обчислювальні вирази (computation expressions або workflows). Вони є узагальненням генераторів послідовностей і, зокрема, дозволяють вбудовувати в F# такі обчислювальні структури, як монади і моноїд. Також вони можуть бути застосовані для асинхронного програмування та створення DSL.

Обчислювальний вираз має форму блоку, що містить певний код на F# у фігурних дужках. Цьому блоку повинен передувати спеціальний об'єкт, який називається ще будівник (builder). Загальна форма наступна: builder {comp-expr}.

Приклад написання обчислювального виразу для роботи з послідовностями, дозволяє створювати та маніпулювати послідовностями в функціональному стилі:let mySequence = seq { yield 1 yield 2 yield 3 }Будівник визначає спосіб інтерпретації того коду, який вказаний у фігурних дужках. Сам код обчислення зовні майже не відрізняється від звичайного коду на F#, крім того, що в ньому не можна визначати нові типи, а також не можна використовувати змінні. Замість таких значень можна використовувати посилання, але робити це слід з великою обережністю, оскільки обчислювальні вирази зазвичай створюю відкладені обчислення, а останні не дуже люблять побічні ефекти.

У залежності від будівника всередині обчислювального виразу можна використовувати особливі конструкції let!, Use!, Return, return!, Yield і yield!. Якщо читач знайомий з мовою програмування Haskell, то можна помітити, що let! відповідає стрілкою з нотації do, а return має той же зміст, що і в Haskell.

За своєю суттю обчислювальне вираз є синтаксичним цукром навколо зазначеного будівника. Компілятор F# проходиться по коду і замінює мовні конструкції викликами відповідних методів будівника b згідно з наступною таблицею: КонструкціяФорма перетворенняlet pat = expr in cexprlet pat = expr in cexprlet! pat = expr in cexprb.Bind(expr, (fun pat -> cexpr))return exprb.Return(expr)return! exprb.ReturnFrom(expr)yield exprb.Yield(expr)yield! exprb.YieldFrom(expr)use pat = expr in cexprb.Using(expr, (fun pat -> cexpr))use! pat = expr in cexprb.Bind(expr, (fun x -> b.Using(x, fun pat -> cexpr))do! expr in cexprb.Bind(expr, (fun () -> cexpr))for pat in expr do cexprb.For(expr, (fun pat -> cexpr))while expr do cexprb.While((fun () -> expr), b.Delay( fun () -> cexpr))if expr then cexpr1 else cexpr2if expr then cexpr1 else cexpr2if expr then cexprif expr then cexpr else b.Zero()cexpr1 cexpr2b.Combine(cexpr1, b.Delay(fun () -> cexpr2))try cexpr with patn -> cexprnb.TryWith(expr, fun v -> match v with (patn:ext) -> cexprn | _ raise exn)try cexpr finally exprb.TryFinally(cexpr, (fun () -> expr)) Основна ідея полягає в тому, що коли компілятор обробляє конструкцію обчислювального виразу, то він намагається викликати відповідний метод будівника. Будівник не зобов'язаний реалізовувати всі вказані методи. Якщо потрібного методу немає, то буде згенерована помилка компіляції.

Асинхронні потоки операцій (async workflows)

Асинхронні потоки операцій — це один з найцікавіших прикладів практичного використання обчислювальних виразів. Код, що виконує будь-які неблокуючі операції введення-виведення, як правило складний для розуміння, оскільки представляє з себе безліч callback-методів, кожен з яких обробляє якийсь проміжний результат і можливо починає нову асинхронну операцію. Асинхронні потоки операцій дозволяють писати асинхронний код послідовно, не визначаючи callback-методи явно. Для створення асинхронного потоку операцій використовується блок async:

Тут ми оголосили функцію getPage, яка повинна повертати вміст сторінки за заданою адресою. Ця функція має тип string--> Async <string> і повертає асинхронну операцію, яка може бути використана для отримання рядка з вмістом сторінки. Варто відзначити, що класи WebRequest і StreamReader не мають методів AsyncGetResponse і AsyncReadToEnd, це методи розширення.

Будівник асинхронного потоку операцій, працює таким чином. Зустрівши оператор let! або do!, він починає виконувати операцію асинхронно, при цьому метод, початківець асинхронну операцію, отримає частину, що залишилася блоку async як функція зворотного виклику. Після завершення асинхронної операції переданий callback продовжить виконання асинхронного потоку операцій, але, можливо, вже в іншому потоці операційної системи (для виконання коду використовується пул потоків). У результаті код виглядає так, як ніби він виконується послідовно. Те, що може бути з легкістю записано всередині блоку async з використанням циклів і умовних операторів, досить складно реалізується з використанням звичайної техніки, що вимагає опису множини callback-методів і передачею стану між їх викликами.

Обробка винятків — найбільш наочний приклад зручності асинхронних потоків операцій. Якщо ми пишемо асинхронний код у традиційному стилі, то кожен метод зворотного виклику повинен обробляти винятки самостійно. Блок async може включати оператор try, за допомогою якого можна обробляти винятки.

У цьому прикладі потік операцій повертає значення типу string ~ option, тобто, або рядок або пусте значення, щоб викликає код міг обробити помилку.

Значення типу Async <_> потрібно передати в один із статичних методів класу Async, щоб розпочати виконання відповідного потоку операцій. У найпростішому випадку можна скористатися методом Async.RunSynchronously, який просто заблокує викликає потік до тих пір, поки всі операції не будуть виконані.

MailboxProcessor

MailboxProcessor — це клас зі стандартної бібліотеки F #, реалізує один з патернів паралельного програмування. MailboxProcessor є агентом23, обробляють чергу повідомлень, які поставляються йому ззовні за допомогою методу Post. Вся конкурентність підтримується реалізацією класу, який містить чергу з можливістю одночасного запису кількома письменниками і читання одним єдиним читачем, яким є сам агент.

Вище наведена реалізація найпростішого агента, який при отриманні повідомлення, що містить рядок, виводить його на екран. Послати агенту повідомлення, як вже було сказано вище, можна за допомогою методу Post:

Цікаво відзначити тип функції, що є єдиним параметром конструктора агента (і статичного методу Start) 24: