Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Введение в функциональное программирование

Введение в функциональное программирование

Краткий обзор основ функционального программирования: от алгебраических типов данных до отдельных элементов теории категорий. Объяснение основных понятий подается в сравнении с традиционным ООП подходом и сопровождается примерами на F#.
Рекомендуется тем, кто уже столкнулся со сложностями ООП проектирования и тем, кто подозревает о наличии более простых решений.

Tech Talks @NSU

March 21, 2019
Tweet

More Decks by Tech Talks @NSU

Other Decks in Programming

Transcript

  1. План • Система типов • Функции, эффекты • Иммутабельность •

    Композиция • Базовые структуры и операции • Функторы, моноиды...
  2. Алгебраические типы данных (ADT) // simple types type Nothing =

    unit type ChannelId = int // Product types type Timestamp = int * DateTime let ts = (0, DateTime.Now) type MyFun = int -> string
  3. Алгебраические типы данных (ADT) type UserInfo = {ident: int; name:

    string} type Channel = { Messages: list<Message> Info: string Users: list<UserInfo> PostText: string } let chan = {Messages = []; Info = "None"; Users = []; PostText = ""} // Sum types // ...
  4. ADT: типы суммы type ChannelMessage = | Init of string

    * list<UserInfo> | Update of string | AppendMessage of Message let cmd = Update “a new topic" type List<'T> = | Nil | Cons of 'T * List<‘T> > let list = Cons(1, Cons(2, Cons (3, Nil))) val list : List<int> = Cons (1,Cons (2,Cons (3,Nil)))
  5. Пример системы классов в ООП class Point { public int

    X, Y; } class Size { public int Width, Height; } abstract class Shape { abstract void draw(/* where!? */); abstract int s(); } class Rectangle: Shape { public Point Origin; public Size Size; public override int s() {return Size.Width * Size.Height;} } ...
  6. Пример системы (продолжение) Осталось еще: • позаботиться о конструкторах •

    сделать инкапсуляцию • решить от чего наследовать квадрат и прямоугольник • реализовать сравнение • подстраховаться от нечаянной мутации • написать юнит-тесты • ...
  7. То же на F# type Point = { x: int;

    y: int } type Size = { width: int; height: int } type Shape = | Rectangle of origin: Point * size: Size | Circle of center: Point * radius:int | Square of origin: Point * side: int let s shape = ... // оставим это на потом
  8. Конструирование “объектов” > let o = { x = 0;

    y = 0} > let shapes = [ Circle (o, 100) Square (o, 50) Rectangle ({x = 100; y = 80}, { width = 40; height = 30}) ] val o : Point = {x = 0; y = 0;} val shapes : Shape list = [Circle ({x = 0; y = 0;},100); Square ({x = 0; y = 0;},50); Rectangle ({x = 100; y = 80;},{width = 40; height = 30;})]
  9. Иммутабельность > let opoint = { x = 10; y

    = 20 } > let somePoint = { o with x = 30 } val opoint: { x = 10; y = 20 } val somePoint: { x = 30; y = 10 }
  10. Выражения • `let` - связывание имени • match, if/else •

    try/catch, try/finally let a = 1 + 2 in computation a // ==> (fun a -> computation a) (1 + 2)
  11. Pattern matching • операция обратная конструированию объектов • записи, кортежи,

    DU, списки и пр. • любое связывание имени let ssz ({width=w; height=h}) = w * h let s shape = match shape with | Circle (_, radius) -> Math.Pi * radius * radius | Square (_, side) -> side * side | Rectangle (_, size) -> ssz size | _ -> 0
  12. Вывод типов let s a = match a with |

    Circle (_, radius) -> Math.Pi * radius * radius ... • алгоритм Хиндли-Миллнера • тип `a` - Shape • тип результата `double` (`float` в F#)
  13. Функции • чистота • эффекты • декларативность • предопределенность •

    тотальность > let square shape = match shape with | Square _, side -> side * side warning FS0025: Incomplete pattern matches on this expression. For example, the value '(Circle (_, _),_)' may indicate a case not covered by the pattern(s).
  14. Пример обновления данных C#: type Car = { Make: string;

    Model: string; Mileage: int } type Editor = { Name: string; Salary: int; Car: Car } type Book = { Name: string; Author: string; Editor: Editor } aBook.Editor.Car.Mileage += 1000;
  15. Пример обновления данных (F#) • Неудобно! let book2 = {

    aBook with Editor = { aBook.Editor with Car = { aBook.Editor.Car with Mileage = aBook.Editor.Car.Mileage + 1000 } } }
  16. Линзы type Lens<'a,'b> = { Get: 'a -> 'b; Set:

    'b -> 'a -> 'a } // Lens<Car, int> let carMileage = { Get = fun (c: Car) -> c.Mileage Set = fun v (x: Car) -> { x with Mileage = v } } // Lens<Editor,Car> let editorCar = { Get = fun (x: Editor) -> x.Car Set = fun v (x: Editor) -> { x with Car = v } } // Lens<Book,Editor> let bookEditor = { Get = fun (x: Book) -> x.Editor Set = fun v (x: Book) -> { x with Editor = v } }
  17. Линзы let inline (>>|) (l1: Lens<'a,'b>) (l2: Lens<'b,'c>) : Lens<'a,'c>

    = let setter value o = o |> l1.Set (o |> (l1.Get >> l2.Set value)) in { Get = l1.Get >> l2.Get; Set = setter } let bookEditorCarMileage = bookEditor >>| editorCar >>| carMileage let mileage = bookEditorCarMileage.Get aBook let book2' = bookEditorCarMileage.Set (mileage + 1000) aBook // C#: aBook.Editor.Car.Mileage += 1000;
  18. Встроенные структуры: list type list<’t> = Nil | Cons of

    head:'t * tail: list<’t> let list1 = [1; 2; 3] let list2 = 1 :: 2 :: [3; 4] let list3 = [ for i in list1 -> i * i ] // деконструирование let i1::i2::rest = list3
  19. Обработка списков Можно обрабатывать список так: int sq(List<Shape> shapes) {

    var sq = 0; for(var i = 0; i < list1.Length; i++) { sq += list1[i].s(); } return sq; } Или так: let rec sq (list: Shape list) = match list with | [] -> 0 | x::tail = (s x) + sq tail
  20. List.fold (свертка) let sq list = List.fold (fun sq shape

    -> sq + s shape) 0 list let sum = List.fold (fun a b -> a + b) 0 // или даже let sq = List.fold (fun a sh -> a + s sh) 0
  21. Обработка списков 2 Или, обобщая: let listShiftBy dx dy ls

    = match ls with | [] -> [] | x::tail = shiftBy dx dy x :: listShiftBy dx dy tail let map f = function | [] -> [] | x::tail = f x :: map f tail let listShiftBy dx dy = map (shiftBy dx dy)
  22. Пример из жизни • список имен для поиска в тексте

    • приоритет у более длинного имени // sort names to put prefix past (p1 first, than p) var orderedNames = new string[names.Count]; foreach (var name1 in names.Keys) { int index = 0; foreach (var name2 in names.Keys) { if (name1 != name2) if (name2.StartsWith(name1, StringComparison.InvariantCultureIgnoreCase)) index++; } orderedNames[index] = name1; }
  23. Моноид let sum a b = a + b let

    aggregateSum = List.fold sum 0 // int list -> int > let s = sum [2;3;6] val s : int = 11 1. a+b = b+a 2. (a+b)+c = a+(b+c) 3. a+0 = a
  24. Моноид 1. a+b = b+a 2. (a+b)+c = a+(b+c) 3.

    a+0 = a let avg a b = (a + b)/2 let aggregateAvg = List.fold avg 0.0 // float list -> float > let a,b = avg [2;3;6], avg [6;2;3] val a : float = 4.25 val b : float = 5.5
  25. Как сделать моноид type Average = { count: int; sum:

    float } let identity = { count = 0; sum = 0.0 } let combine a1 a2 = { count = a1.count + a2.count; sum = a1.sum + a2.sum } let lift v = { count = 1; sum = v} let getValue { count = c; sum = s} = s/(float)c let calcAvg = List.map lift >> List.fold combine identity >> getValue let a = calcAvg [6.0; 2.; 3.]
  26. Почему полезно знать ФП • упражнение для мозга • новые

    способы решения для новых задач • формализм • тотальность