F#
Экспресс-тур по F#: функциональный язык .NET за 16 минут — let, конвейер |>, pattern matching, записи, размеченные объединения, Option.
F# — функциональный язык первого класса на платформе .NET: неизменяемость по умолчанию, мощный вывод типов, сопоставление с образцом и идиома конвейера. Весь язык на одной странице через закомментированный код.
Вывод и комментарии
// Однострочный комментарий начинается с двух слешей
(* Многострочный комментарий
занимает несколько строк *)
/// Тройной слеш — XML-документация (подсказки в IDE)
printfn "Привет, F#!" // печать с переводом строки -> Привет, F#!
printf "без перевода" // печать без перевода строки
printfn "%d + %d = %d" 2 3 (2 + 3) // форматированный вывод -> 2 + 3 = 5
Значения и типы
Через let связывают имя со значением. Значения неизменяемы по умолчанию, типы выводятся автоматически.
let x = 42 // int — вывод типа сам понял, что это целое
let pi = 3.14 // float (double)
let flag = true // bool
let name = "Аня" // string
let ch = 'A' // char
// Значение неизменяемо: повторное let — это новое связывание (shadowing),
// а не присваивание
// x = 99 // это НЕ присваивание, а сравнение (вернёт false)
// Явная аннотация типа, если нужно
let count : int = 10
let ratio : float = 2.5
// Числовые суффиксы
let big = 1_000_000L // int64 (нижнее подчёркивание — разделитель разрядов)
let small = 1uy // byte
let d = 9.99m // decimal
printfn "%s, тебе %d" name count // -> Аня, тебе 10
Функции
Функция — тоже значение. Аргументы пишут через пробел, без скобок и запятых.
// Функция двух аргументов; тело — выражение после =
let add x y = x + y
printfn "%d" (add 2 3) // -> 5
// Каррирование и частичное применение: применяем не все аргументы
let add10 = add 10 // add10 : int -> int
printfn "%d" (add10 5) // -> 15
// Анонимная функция (лямбда) через fun
let square = fun x -> x * x
printfn "%d" (square 4) // -> 16
// Композиция функций оператором >> (сначала f, затем g)
let inc x = x + 1
let double x = x * 2
let incThenDouble = inc >> double // (x+1)*2
printfn "%d" (incThenDouble 3) // -> 8
// << — композиция в обратном порядке
let doubleThenInc = inc << double // x*2+1
printfn "%d" (doubleThenInc 3) // -> 7
// Многострочная функция: тело по отступу (значимые отступы!)
let describe n =
let half = n / 2
half + 1 // последнее выражение — результат
Конвейер |> (pipe forward)
Главная идиома F#: x |> f то же, что f x. Читается слева направо, как поток данных.
// Без конвейера — вложенные вызовы читаются «изнутри наружу»
let r1 = List.sum (List.map square [1; 2; 3]) // -> 14
// С конвейером — читается как цепочка преобразований
let r2 =
[1; 2; 3]
|> List.map square // [1; 4; 9]
|> List.sum // 14
printfn "%d" r2 // -> 14
// |> передаёт значение последним аргументом следующей функции
5 |> add 10 |> printfn "%d" // add 10 5 -> 15
// <| — конвейер влево (реже): f <| x то же, что f x
printfn "%d" <| 2 + 3 // -> 5
Строки
let hello = "Привет"
let world = "мир"
// Конкатенация оператором +
let phrase = hello + ", " + world + "!" // "Привет, мир!"
// Интерполяция строк ($"...") — F# 5.0+
let n = 7
let msg = $"Число равно {n}, а его квадрат {n * n}"
printfn "%s" msg // -> Число равно 7, а его квадрат 49
// printfn-спецификаторы: %s строка, %d целое, %f дробь, %b bool, %A любое
printfn "%s %d %.2f %b" "abc" 42 3.14159 true // -> abc 42 3.14 true
// Многострочный/дословный литерал @"..." (без экранирования)
let path = @"C:\temp\file.txt"
// Методы .NET доступны напрямую на строках
printfn "%d" hello.Length // -> 6
printfn "%s" (hello.ToUpper()) // -> ПРИВЕТ
printfn "%b" (phrase.Contains "мир") // -> true
printfn "%s" (" край ".Trim()) // -> край
Условия
В F# if/then/else — это выражение, оно возвращает значение. Ветки должны быть одного типа.
let n = 5
// if как выражение: результат присваивается переменной
let sign =
if n > 0 then "положительное"
elif n < 0 then "отрицательное"
else "ноль"
printfn "%s" sign // -> положительное
// Однострочный вариант
let absN = if n < 0 then -n else n // -> 5
// match — мощнее if: сопоставление с образцом
let classify x =
match x with
| 0 -> "ноль"
| 1 -> "один"
| _ -> "много" // _ — образец «что угодно»
printfn "%s" (classify 1) // -> один
Коллекции
// Список (immutable связный список) — квадратные скобки, через ;
let nums = [1; 2; 3; 4; 5]
let emptyList : int list = []
let consed = 0 :: nums // :: добавляет в начало -> [0; 1; 2; 3; 4; 5]
let joined = [1; 2] @ [3; 4] // @ соединяет -> [1; 2; 3; 4]
// Диапазон
let oneToTen = [1..10] // [1; 2; ...; 10]
let evens = [2..2..10] // с шагом 2 -> [2; 4; 6; 8; 10]
// Массив (mutable, фикс. размер) — [| ... |]
let arr = [| 1; 2; 3 |]
arr.[0] <- 99 // элементы массива изменяемы
printfn "%d" arr.[0] // -> 99
// Последовательность (seq, ленивая, как IEnumerable)
let lazyNums = seq { 1..1000000 } // вычисляется по требованию
// Кортеж (tuple) — фиксированный набор значений разных типов
let point = (3, 4) // int * int
let (px, py) = point // распаковка -> px=3, py=4
printfn "%d, %d" px py // -> 3, 4
printfn "%d" (fst point) // fst -> 3
printfn "%d" (snd point) // snd -> 4
Работа со списками
let xs = [1; 2; 3; 4; 5]
// map — применить функцию к каждому элементу
let squared = xs |> List.map (fun x -> x * x) // [1; 4; 9; 16; 25]
// filter — оставить подходящие
let evens = xs |> List.filter (fun x -> x % 2 = 0) // [2; 4]
// fold — свёртка с аккумулятором (здесь сумма; 0 — начальное значение)
let total = xs |> List.fold (fun acc x -> acc + x) 0 // -> 15
// Готовые агрегаты
printfn "%d" (List.sum xs) // -> 15
printfn "%d" (List.max xs) // -> 5
printfn "%d" (List.length xs) // -> 5
// Генератор списка (list comprehension) с условием
let squaresOfEvens = [ for x in 1..10 do if x % 2 = 0 then yield x * x ]
printfn "%A" squaresOfEvens // -> [4; 16; 36; 64; 100]
// Цепочка через конвейер
let result =
[1..10]
|> List.filter (fun x -> x % 2 = 1) // нечётные
|> List.map (fun x -> x * x) // в квадрат
|> List.sum // суммируем
printfn "%d" result // -> 165
Размеченные объединения
Discriminated union — тип «одно из перечисленного», часто с данными. Идеален для моделирования вариантов.
// Тип «фигура» — один из нескольких вариантов, каждый со своими данными
type Shape =
| Circle of radius: float
| Rectangle of width: float * height: float
| Point
let area shape =
match shape with
| Circle r -> 3.14159 * r * r
| Rectangle (w, h) -> w * h
| Point -> 0.0
printfn "%.2f" (area (Circle 2.0)) // -> 12.57
printfn "%.2f" (area (Rectangle (3.0, 4.0))) // -> 12.00
// Option — встроенное объединение для «значение есть / нет»
// type Option<'T> = Some of 'T | None
let tryDivide a b =
if b = 0 then None // нет результата
else Some (a / b) // есть результат
match tryDivide 10 2 with
| Some v -> printfn "Результат: %d" v // -> Результат: 5
| None -> printfn "Деление на ноль"
// defaultValue — значение по умолчанию, если None
printfn "%d" (tryDivide 10 0 |> Option.defaultValue -1) // -> -1
Записи
Record — именованный набор полей. Неизменяем; для «изменения» создают копию через with.
// Объявление типа-записи
type Person = { Name: string; Age: int }
// Создание — поля выводят тип записи автоматически
let alice = { Name = "Алиса"; Age = 30 }
printfn "%s, %d" alice.Name alice.Age // -> Алиса, 30
// Копия с изменённым полем (исходная запись не меняется)
let older = { alice with Age = 31 }
printfn "%d" alice.Age // -> 30 (оригинал цел)
printfn "%d" older.Age // -> 31
// Записи сравниваются и печатаются структурно «из коробки»
printfn "%b" (alice = { Name = "Алиса"; Age = 30 }) // -> true
printfn "%A" older // -> { Name = "Алиса"; Age = 31 }
// Деструктуризация записи в match/let
let { Name = nm; Age = ag } = alice
printfn "%s %d" nm ag // -> Алиса 30
Сопоставление с образцом
match — сердце F#: разбор по структуре, охранные условия и активные шаблоны.
// Образцы списков: пустой, один элемент, голова :: хвост
let rec sumList lst =
match lst with
| [] -> 0 // пустой список
| head :: tail -> head + sumList tail // голова + сумма хвоста
printfn "%d" (sumList [1; 2; 3]) // -> 6
// Охранное условие (guard) через when
let grade score =
match score with
| s when s >= 90 -> "A"
| s when s >= 75 -> "B"
| _ -> "C"
printfn "%s" (grade 80) // -> B
// Сопоставление кортежей
let describePoint p =
match p with
| (0, 0) -> "начало координат"
| (x, 0) -> $"на оси X: {x}"
| (0, y) -> $"на оси Y: {y}"
| (x, y) -> $"точка ({x}, {y})"
printfn "%s" (describePoint (5, 0)) // -> на оси X: 5
// Активный шаблон (active pattern) — свои именованные образцы
let (|Even|Odd|) n = if n % 2 = 0 then Even else Odd
let parity n =
match n with
| Even -> "чётное"
| Odd -> "нечётное"
printfn "%s" (parity 7) // -> нечётное
Императивные элементы и .NET
F# умеет и в мутабельность, классы и всю экосистему .NET, когда это нужно.
// mutable — изменяемая переменная; присваивание оператором <-
let mutable counter = 0
counter <- counter + 1
counter <- counter + 1
printfn "%d" counter // -> 2
// Циклы (императивный стиль)
for i in 1..3 do
printf "%d " i // -> 1 2 3
printfn ""
let mutable k = 0
while k < 3 do
k <- k + 1
// Класс с конструктором, полем и методом
type Counter(start: int) =
let mutable value = start // приватное изменяемое поле
member this.Value = value // свойство только для чтения
member this.Increment() = // метод
value <- value + 1
let c = Counter(10)
c.Increment()
c.Increment()
printfn "%d" c.Value // -> 12
// Прямое использование библиотек .NET
open System
printfn "%f" (Math.Sqrt 16.0) // -> 4.000000
printfn "%s" (DateTime.Now.ToString "yyyy-MM-dd") // текущая дата
printfn "%d" (Int32.Parse "123" + 1) // -> 124
// Безопасный разбор через .NET с кортежем результата
match Int32.TryParse "42" with
| true, v -> printfn "Разобрано: %d" v // -> Разобрано: 42
| false, _ -> printfn "Не число"