Краткое введение в FSharp

Введение

F# (эф-шарп) разработан в Microsoft Corporation в 2005 году. Информация о языке и необходимое програмное обеспечение доступно по адресу http://fsharp.org. Имеется поддержка языка в популярном пакете разработчика Visual Studio, он также доступен для Linux и Mac OS X.

Особенности программирования на FSharp

Замечание Все дальнейшие примеры приведены для интерактивной оболочки. Символ '>' всегда обозначает приглашение интерпретатора и не набирается пользователем

REPL

При использовании MS Visual Studio можно открыть окно F# interactive и вводить команды. Можно выделить в программе текст и послать его в REPL комбинацией: Alt+Enter.

Вне Visual Studio запустить интерпретатор можно программой fsi.

Примеры использования .NET

> open System;;
> let now = DateTime.Now;;

val now : DateTime = 06.11.2016 15:29:42

>Math.PI;;
val it : float = 3.141592654

Комментарии

// - однострочный комментарий
(*
 Многострочный комментарий
 (может занимать несколько строк)
*)

Данные

Стандартные типы

Ниже перечислены основные типы с указанием суффиксов при связывании значений с именами:

Присвоение значений

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

Примеры создания значений:

let a = 7uy
let b = -18s
let c = 0b1010101y

Примеры с возведением в степень:

> let d = 2UL 56;;
val b : uint64 = 72057594037927936UL

Преобразования типов

Явные преобразования типов необходимы, поскольку в языке практически отсутствуют неявные преобразования.

Функции преобразования имеют такие же названия, что и типы, например:

>let e = uint64 2;;
val e : uint64 = 2UL

Сверхдлинные числа

>open System.Numerics;;
>let f = 2I ** 200 ;;
val f : BigInteger = 
  16069380442589902755419620923411626.......

Символы и строки

Все символы хранятся в юникоде:

>let ch = 'л';;
val ch : char = 'л'
> int ch;;
vat it : int = 1083

Строки:

>let hello = "hello, world!";;
val hello : string = "hello, world!"
> hello.[1]
val it : char = 'e'

Структуры данных

В качестве структур данных выступают:

Списки

Списки являются основной структурой данных в функцинальных языках.

Задать список легко:

>let list = [1;2;3];;
val list : int list = [1; 2; 3]

Можно указывать диапазоны значений:

> let list = [1..10];;
val list : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

> let list = ['a'..'z'];;
val list : char list =
  ['a'; 'b'; 'c'; 'd'; 'e'; 'f'; 'g'; 'h'; 'i'; 'j'; 'k'; 'l'; 'm'; 'n'; 'o';
   'p'; 'q'; 'r'; 's'; 't'; 'u'; 'v'; 'w'; 'x'; 'y'; 'z']

Пример определения списка с шагом:

> let lst = [1 .. 2 .. 10];;
val lst : int list = [1; 3; 5; 7; 9]

Мы можем использовать для создания списка генераторы, - специальные конструкции, заключенные в квадратные скобки:

> let evens = [
  for i in 1..10 do
    if i % 2 = 0 then
      yield i
]
val evens : int list = [2; 4; 6; 8; 10]

Вариант с использованием лямбда-функции:

let lst = [for i in 1..10 -> i*i] // [1, 4, 9,..]

Кортежи

..

Массивы

..

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

..

Операторы и операции

В языке F# имеется набор операторов, поддерживающих управляющую логику и циклы.

..
let choice a b =
   if a>b then 'a' else 'b'

Обработка списка циклом for:

> let lst = [1..10];;
val lst : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

> for item in lst do printfn "%A" item;;
1
2
3
4
5
6
7
8
9
10
val it : unit = ()

Оператор yield используется для возвращения элемента из функции или цикла, например, для формирования списка (см. пример в разделе "списки").

Функции

Для функционального языка программирования функция - центральное понятие.

>let square = x * x;;
val square : int -> int

>square 2;;
val it : int = 4

Функция square принимает параметр int и возвращает результат типа int.

Другая функция использует в работе ранее определенную:

> let sum_of_squares x y = square x + square y;;
val sum_of_squares : x:int -> y:int -> int

> sum_of_squares 3 4;;
val it : int = 25

Странный тип выражения становится понятным, если рассмотреть следующий пример:

> let add x y = x + y;;
val add : int -> int -> int

В данном случае функция add принимает два параметра и возвращает функцию, принимающую один параметр, возвращая, в свою очередь, целое число. Этот пример позволяет понять свойство карриуемости:

Функции могут быть каррированными, то есть в них не передается весь набор параметров сразу:

> let add x y = x + y;;
val add : x:int -> y:int -> int

> let inc = add 1;;
val inc : (int -> int)

> inc 5;;
val it : int = 6

Можно явно указать типы параметров:

>let add (x : float) y = x + y;;
val add : float -> float -> float

Параметр обобщенного типа:

>let prn (x : 'a) = x;;
val prn : 'a -> 'a

В качестве обобщенного типа может использоваться любой тип.

Безымянные функции (лямбда-функции) задаются без имени:

> fun x -> x * x;;
val it : x:int -> int = <fun:clo@9>
> it 6;;
val it : int = 36

Лямбда-функции являются очень удобным инструментом при обработках элементов структур данных.

О важности пробелов (отступов)

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

> let mid x y =
-   let sum a b =
-     a + b
-   (sum x y) / 2;;
val mid : x:int -> y:int -> int

> mid 4 2;;
val it : int = 3

Рекурсия

Рекурсивные функции используются для реализации циклических алгоритмов:

> let rec factor n = 
      if n<=1 then 
         1 
      else 
         n * factor (n - 1);;

val factor : n:int -> int

> factor 10;;
val it : int = 3628800

Генераторы списков

Очень полезны генераторы (специальные функции для генерации значений):

..

Литература