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

CodeFest 2019. Вагиф Абилов (Miles) — F# как лекарство от наболевшего — исповедь ветерана ООП

CodeFest
April 06, 2019

CodeFest 2019. Вагиф Абилов (Miles) — F# как лекарство от наболевшего — исповедь ветерана ООП

Несколько лет назад, устав от мутирующих структур данных, синхронизации потоков и громоздкости бизнес-объектов, мы перешли на F#. Тот факт, что наш новый проект запущен и непрерывно используется, относит его к категории успешных, но какова роль языка F# в этом успехе? Достигли бы мы той же скорости разработки и стабильности системы, оставшись с парадигмой ООП (и C# на платформе.NET)? Мы считаем, что F# настраивает разработчиков на стиль программирования, дающий существенные преимущества в проектах с короткими сроками и непрерывным запуском результатов в рабочую среду. Об этих преимуществах и пойдет речь в этом докладе.

CodeFest

April 06, 2019
Tweet

More Decks by CodeFest

Other Decks in Programming

Transcript

  1. Наши инструменты •F#, модель акторов и Akka.NET •Библиотека Akkling (Akka.NET

    F# API) •Провайдеры типов F# для JSON, Yaml и SQL •Microsoft SQL Server для хранения журнал персистентых акторов •Очереди RabbitMQ •Web API на основе Suave.IO •Тестирование с использованием XUnit, FsCheck и TickSpec
  2. Комментарий к беседе F# на Хабре Если F# так хорош,

    как вы его описываете, почему его никто не хочет знать?
  3. Наиболее желанные языки (StackOverflow, опрос 2018) Python 25.1% JavaScript 19%

    Go 16.2% Kotlin 12.4% TypeScript 11.9% Java 10.5% C++ 10.2% Rust 8.3% C# 8.0% Swift 7.7% … … CSS 7.6% SQL 6.8% … … F# 4.0% … …
  4. Наиболее оплачиваемые технологии (там же) F# $74,000 Ocaml $73,000 Clojure

    $72,000 Perl $69,000 Rust $69,000 Erlang $67,000 Scala $67,000 Go $66,000 Ruby $64,000 Bash/Shell $64,000 Coffee Script $63,000 Haskel $60,000 Julia $60,000 TypeScript $60,000 C# $59,000 Objective-C $58,000
  5. Речь пойдет о наболевшем у прагматиков (“pragmatists in pain” *)

    * Эрик Синк “Почему ваш F# евангелизм не работает” https://ericsink.com/entries/fsharp_chasm.html
  6. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  7. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  8. class Point { int X { get; set; } int

    Y { get; set; } Point(int x, int y) { X = x; Y = y } void IncreaseX (int xOffset) { X += xOffset; } void IncreaseY (int yOffset) { Y += yOffset; } }
  9. class Point { int X { get; set; } int

    Y { get; set; } Point(int x, int y) { X = x; Y = y } void IncreaseX (int xOffset) { X += xOffset; } void IncreaseY (int yOffset) { Y += yOffset; } int GetHashCode() {…} bool Equals(object other) {…} }
  10. class Point { readonly int X; readonly int Y; Point(int

    x, int y) { X = x; Y = y } Point IncreaseX (int xOffset) => new Point(x + xOffset, y); Point IncreaseY (int xOffset) => new Point(x, y + yOffset); int GetHashCode() {…} bool Equals(object other) {…} }
  11. Закон Амдала в действии Если у вас 10 процессоров, но

    вы распараллеливаете лишь 40% кода, то быстродействие увеличивается в 1,56 раза
  12. class Point { int X { get; set; } int

    Y { get; set; } Point(int x, int y) { X = x; Y = y } void IncreaseX (int xOffset) { X += xOffset; } void IncreaseY (int yOffset) { Y += yOffset; } }
  13. Структуры данных в F# type Point = { X :

    int Y : int } let p = { X = 1; Y = 2 } let q = { p with X = p.X+1 }
  14. Модули как островки действия бизнес-логики type Point = { X

    : int Y : int } module Point = let increaseX v p = { p with X = p.X+v } let increaseY v p = { p with Y = p.Y+v }
  15. Модули как островки действия бизнес-логики type Point = { X

    : int Y : int } module Point = let increaseX v p = { p with X = p.X+v } let increaseY v p = { p with Y = p.Y+v } let v = { X = 5; Y = 6 } let z = p |> Point.increaseX 1
  16. Управление видимостью методов бизнес-логики type Point = {…} module PointUpdate

    = let increaseX v p = { p with X = p.X+v } let increaseY v p = { p with Y = p.Y+v } open PointUpdate let v = { X = 5; Y = 6 } let z = p |> increaseX 1
  17. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  18. Реализация «Игры жизни» Конвея let isAlive population cell = population

    |> List.exists ((=) cell) let aliveNeighbours population cell = neighbours cell |> List.filter (isAlive population) let survives population cell = aliveNeighbours population cell |> List.length |> fun x -> x >= 2 && x <= 3 let reproducible population cell = aliveNeighbours population cell |> List.length = 3 let allDeadNeighbours population = population |> List.collect neighbours |> Set.ofList |> Set.toList |> List.filter (not << isAlive population) let nextGeneration population = List.append (population |> List.filter (survives population)) (allDeadNeighbours population |> List.filter (reproducible population))
  19. Вывод конкретного типа let neighbours (x, y) = [ for

    i in x-1..x+1 do for j in y-1..y+1 do if not (i = x && j = y) then yield (i,j) ]
  20. Вывод конкретного типа let neighbours (x, y) = [ for

    i in x-1..x+1 do for j in y-1..y+1 do if not (i = x && j = y) then yield (i,j) ] let neighbours (x, y, z) = [ for i in x-1..x+1 do for j in y-1..y+1 do for k in z-1..z+1 do if not (i = x && j = y && k = z) then yield (i,j,k) ]
  21. Реализация «Игры жизни» Конвея let isAlive population cell = population

    |> List.exists ((=) cell) let aliveNeighbours population cell = neighbours cell |> List.filter (isAlive population) let survives population cell = aliveNeighbours population cell |> List.length |> fun x -> x >= 2 && x <= 3 let reproducible population cell = aliveNeighbours population cell |> List.length = 3 let allDeadNeighbours population = population |> List.collect neighbours |> Set.ofList |> Set.toList |> List.filter (not << isAlive population) let nextGeneration population = List.append (population |> List.filter (survives population)) (allDeadNeighbours population |> List.filter (reproducible population))
  22. Вывод конкретного типа type Color = | Red | Orange

    | Yellow | Green | Blue | Indigo | Violet let neighbours color = match color with | Red -> [Red; Orange] | Orange -> [Red; Orange; Yellow] | Yellow -> [Orange; Yellow; Green] | Green -> [Yellow; Green; Blue] | Blue -> [Green; Blue; Indigo] | Indigo -> [Blue; Indigo; Violet] | Violet -> [Indigo; Violet]
  23. Пример кода из нашего проекта let sendPublicEvent storage state getPath

    getRequest (event : OddjobEvent) = let requestContent = getRequest event.Job storage let details = getPath event.Job storage state |> Option.map toDetails |> Option.defaultValue event.Details publishEvent { event with Details = details }. ToPublicEvent(requestContent)
  24. Пример кода из нашего проекта let sendPublicEvent storage state getPath

    getRequest (event : OddjobEvent) = ... ‘a ‘b Job -> ‘a -> ‘b -> string option Job -> ‘a -> RequestContent
  25. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  26. Алгебраические типы данных в F# type ExpiryDate = { Year

    : int Month : int } type CardNumber = CardNumber of string type PaymentCard = { CardNumber : CardNumber ExpiryDate : ExpiryDate } type BankAccount = BankAccount of string
  27. Алгебраические типы данных в F# type FundingSource = | PaymentCard

    of PaymentCard | BankAccount of BankAccount let isSourceValid source = let now = DateTime.Now match source with | PaymentCard x -> x.ExpiryDate >= { Month = now.Month Year = now.Year } | BankAccount _ -> true
  28. Активные шаблоны в F# let (|Even|Odd|) n = if n

    % 2 = 0 then Even else Odd let printNumberKind n = match n with | Even -> "Even" | Odd -> "Odd"
  29. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  30. DTO

  31. «Один из сценариев, когда стоит пользоваться чем-то типа DTO –

    это когда имеется существенное несовпадение модели слоя презентации и доменной модели нижнего уровня» Мартин Фаулер о локальных DTO 52
  32. Оформление заказа Unvalidated order Validated order Validate Unvalidated order Priced

    order Shipped order Cancelled order Make quotation + TotalPrice + TrackingUrl + Reason Ship Cancel
  33. class Order { … decimal TotalPrice { get; } Uri

    TrackingUrl { get; } string CancellationReason { get; } bool IsValidated { get; } bool IsShipped { get; } bool IsCancelled { get; } }
  34. class Order { … decimal TotalPrice { get; } Uri

    TrackingUrl { get; } string CancellationReason { get; } bool IsValidated { get; } bool IsShipped { get; } bool IsCancelled { get; } void Validate(); void Ship(); void Cancel(); }
  35. class Order { … decimal TotalPrice { get; } Uri

    TrackingUrl { get; } string CancellationReason { get; } bool IsValidated { get; } bool IsShipped { get; } bool IsCancelled { get; } } class OrderManager { void Validate(Order order); void Ship(Order order); void Cancel(Order order); }
  36. class Order { … decimal TotalPrice { get; } Uri

    TrackingUrl { get; } string CancellationReason { get; } bool IsValidated { get; } bool IsShipped { get; } bool IsCancelled { get; } } class OrderManager { void Validate(Order order); void Ship(Order order); void Cancel(Order order); } Чистые объекты Чисто бизнес ничего личного
  37. class UnvalidatedOrder { … } class ValidatedOrder { … }

    class PricedOrder { … decimal TotalPrice { get; } } class ShippedOrder { … Uri TrackingUrl { get; } } class CancelledOrder { … string Reason { get; } } class OrderValidator { ValidatedOrder ValidateOrder(…) } class QuotationMaker { PricedOrder MakeQuotation(…) } class OrderDispatcher { ShippedOrder ShipOrder(…) }
  38. Доменное моделирование в F# type OrderDetails = string list type

    UnvalidatedOrder = { Details : OrderDetails } type ValidatedOrder = { Details : OrderDetails ValidationTime : DateTimeOffset }
  39. Доменное моделирование в F# type PricedOrder = { Details :

    OrderDetails TotalPrice : decimal } type ShippedOrder = { Details : OrderDetails Uri : TrackingUrl } type CancelledOrder = { Details : OrderDetails Reason : string }
  40. Доменное моделирование в F# module OrderProcessing = let validateOrder (order

    : UnvalidatedOrder) = { Details = order.Details ValidationTime = DateTimeOffset.Now } let priceOrder totalPrice (order : ValidatedOrder) = { Details = order.Details TotalPrice = totalPrice } let shipOrder trackingUrl (order : PricedOrder) = { Details = order.Details TrackingUrl = trackingUrl }
  41. Доменное моделирование в F# open OrderProcessing let order = {

    Details = ["book"] } |> validateOrder |> priceOrder 9.90m |> shipOrder (Uri "http://www.orders.com/40395874")
  42. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  43. Нулевые ссылки следует избегать, не просто заменяя их на типы

    опций, а вообще избегая появления в структурах данных таких полей
  44. Без полей-опции не обойтись при приеме данных извне, но они

    разрушают определенность бизнес-логики
  45. Зачем мы передаем необязательные данные? •Чтобы в одном методе обработать

    различные сценарии •Не лучше ли разбить такой метод на несколько? •Чтобы сохранить данные, которые могут потребоваться на следующих этапах обработки данных •Есть ли возможность спрятать данные, не исользуемые на текушем этапе обработки?
  46. Рич Хики – Может быть нет •Maybe/Either не являются частью

    системы алгебраических типов •Они скорее являют собой свидетельство недостаточной выразительности системы типов •Either – ерунда размывает смысл •Лишен ассоциативности/коммутативности/симметрии/ возможности компоновки https://www.youtube.com/watch?v=YR5WdGrpoug
  47. Лечение наболевшего 1. Неизменяемость структур данных (immutability) 2. Вывод типов

    (type inference) 3. Алгебраические типы и распознавание шаблонов (pattern matching) 4. Отказ от бизнес-объектов 5. Отсутствие null (и осторожность с option)
  48. Воздействие F# на цикл разработки 1. Алгебраические типы лучше подходят

    для выражения функциональных требований 2. Небольшие неизменяемые структуры (records) эффективны для представления данных каждого этапа бизнес-процесса
  49. Воздействие F# на цикл разработки 1. Алгебраические типы лучше подходят

    для выражения функциональных требований 2. Небольшие неизменяемые структуры (records) эффективны для представления данных каждого этапа бизнес-процесса 3. Отказ от нулей (и по большей части опций) делает бизнес-логику компактной и прямолинейной
  50. Воздействие F# на цикл разработки 1. Алгебраические типы лучше подходят

    для выражения функциональных требований 2. Небольшие неизменяемые структуры (records) эффективны для представления данных каждого этапа бизнес-процесса 3. Отказ от нулей (и по большей части опций) делает бизнес-логику компактной и прямолинейной 4. Модули позволяют для каждого сценария делать видимыми лишь нужные для него методы
  51. Воздействие F# на цикл разработки 1. Алгебраические типы лучше подходят

    для выражения функциональных требований 2. Небольшие неизменяемые структуры (records) эффективны для представления данных каждого этапа бизнес-процесса 3. Отказ от нулей (и по большей части опций) делает бизнес-логику компактной и прямолинейной 4. Модули позволяют для каждого сценария делать видимыми лишь нужные для него методы