Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Столпы функционального программирования для адептов ООП, Николай Мозговой
1.
2. About me
Name: Nikolay Mozgovoy (FB, LinkedIn)
Developer in Sigma Software since 2013
and mentor since 2016
Teacher in KhAI (Department Of Computer
Systems, Networks And Cybersecurity)
GGJ Ukraine prizewinner (2016, 2017)
GGJ Site organizer (Kharkiv)
Media/Global hack weekend competitor
MSCSD: App builder
Caught Lisp in 2016
6. Lambda calculus
Formal system in mathematical logic for expressing
computation based on function abstraction and
application using variable binding and substitution
Turing complete
Developed by Alonzo Church in the 1930s
Notation:
λx. t[x] – function that do anything with x
λx. x^2 – x squaring function same as y=x^2
(λx y. x + y) 1 2 = (λy. 1 + y) 2 = 1 + 2 – carrying
11. Imperative code & parallel execution
BigInteger[] arr = Enumerable.Range(0, 10000)
.Select(x => new BigInteger(x)).ToArray();
BigInteger sum = 0;
foreach (var n in arr) sum += n; // OK
Parallel.For(0, arr.Length, n => sum += n); // Wrong result
// And if we apply lock it would degradate to de-facto single-threaded code
arr.AsParallel().Aggregate((a, b) => a + b); // OK
12. Functional code & parallel execution
1
3
2 43
7
10
(a, b) => a + b (a, b) => a + b
(a, b) => a + b
16. The failure of state
public void ProcessPackage(Stream package)
{
SaveMetadataInDb(package);
package.Seek(0, SeekOrigin.Begin); // !!!
/* ... */
SaveContentInSearchEngine(package);
/* ... */
LogOperation(package);
}
17. Immutability
Impossibility to change the state of the object once it was created
Immutable objects are simpler to test, and use
Truly immutable objects are always thread-safe
They help to avoid temporal coupling
Their usage is side-effect free (no defensive copies)
Identity mutability problem is avoided
They always have failure atomicity
They are much easier to cache
They prevent NULL references, which are bad
I the Lord do not change (Malachi 3:6)
18. Immutability example
; LISP
(define animals (list 'dog 'cat 'fish))
(cons 'horse animals) ; returns a new list of animals, not changing existing one
// F#
let animals = ["dog";"cat";"rat"]
let moreAnimals = List.append animals ["horse"] // returns a new list
// C#
var animals = new ReadOnlyCollection<string>(new[] { "dog", "cat", "fish" });
var moreAnimals = animals.Append("horse"); // returns new Enumerable
19. The sin of impurity
public async Task HookNextBuild(IProject project, Task buildFinished) {
var buildStarted = SyncEvents.VsBuildStarted(this.VsPid).ToTask(); // IPC
await Task.WhenAny(buildStarted, buildFinished).ConfigureAwait(false);
if (this.recentAnalysisCompletion != null) {
await this.recentAnalysisCompletion;
this.recentAnalysisCompletion = null;
this.recentCompletionTime = DateTime.Now; // Time
}
var analysisCompletion = await this.LaunchAnalysisAsync(project);
this.recentAnalysisCompletion = analysisCompletion;
}
20. Purity
The property of functions not to have side-effects and any dependence from
external state or time
Function is considered pure when:
The function always evaluates the same result value given the same
argument value(s)
Evaluation of the result does not cause any side effect
Application:
Simplifies testing
Simplifies parallel computing
Allows memoization
21. Purity example
// in F#
let inc x = x + 1 // pure function
let mutable i = 0
let badInc () = i <- i + 1 // impure function
; in LISP
(define (inc x) (+ x 1)) ; pure function
(define i 1)
(define (bad-inc!) (set! i (+ i 1)))
// in C#
Math.Pow(2, 8); // pure function
Path.GetTempFileName() ; // impure function
DateTime.Now; // impure function
22. First-class and Higher-order functions
First-class functions is a property of programming language to treat functions
as a standard datatype
Function is considered higher-order function when it does at least one of the
following:
takes one or more functions as arguments
returns a function as its result
23. First-class and Higher-order functions
// C#
// toUpper is first-class function
Func<string, string> toUpper = str => str.ToUpper();
// Select is higher-order function
new[] { "dog", "cat" }.Select(toUpper);
// F#
// fun x -> -x is first-class function & Seq.sortBy is higher-order function
[22;31;1;5;7] |> Seq.sortBy(fun x -> -x)
// >> is a higher-order function, it returns a composition of the functions
(add1 >> add1) 1 // 3
24. Recursion
Recursion is idiomatic way to perform iterations or lopping in functional
programming languages
Tail recursion usually can be recognized and optimized by a compiler
Mutual recursion may require trampoline to handle it
To iterate is human, to recurse, divine (L. Peter Deutsch)
25. Recursion example (LISP)
; mutual recursion
(define (r1 i)
(if (< i 0) 1 (r2 (- i 1))))
(define (r2 i)
(if (< i 0) 1 (r1 (- i 1))))
(r1 1000000)
; tail recursion
(define (factorial x)
(if (< x 2)
1
(* x (factorial (- x 1)))))
26. Recursion example (F#)
// mutual recursion
let rec r1 i =
if i < 0
then 1
else r2(i - 1)
and r2 i =
if i < 0
then 1
else r1(i - 1)
// tail recursion
let rec factorial x =
if x < 2
then 1
else x * factorial(x - 1)
28. Closures
A data structure containing a lambda expression, and an environment to be
used when that lambda expression is applied to arguments
Closures are used to:
bind together data and behavior (just like objects in OOP)
emulate state
emulate object system
function
environment
in which
function got
created
closure
object
29. Closure example in F# (simple and artificial)
// creates a function that raises y to a power of x
let makePowerFn x =
(fun y -> pown y x) // this lambda is a closure itself (because of reference to x)
let pow2 = makePowerFn 2
pow2 3 // 9
let pow3 = makePowerFn 3
pow3 3 // 27
30. Closure example: pure recursive data structures
type Seq = { value: int; next: (unit -> Seq) }
let rec initSeq next value : Seq = {
value = value; // return current value
next = (fun () -> initSeq next (next value)) // create a new sequence with the next value
}
let pow2Seq = initSeq (fun x -> x * 2) 2 // sequence of powers of 2
pow2Seq.value // 2
pow2Seq.next().next().value // 8
// doesn’t it look like enumerator.MoveNext().MoveNext().Current in C#?
31. Closure example: trampoline (in JavaScript)
function factorial (n) {
return n < 2
? 1
: n * factorial(n -1);
}
factorial(3); // 6
/* maximum call stack
size exceeded */
factorial(100000);
function trampoline (fn) {
while (
typeof fn === 'function') {
fn = fn();
}
return fn;
}
function factorial (n, acc = 1) {
return function () {
return (n < 2)
? acc
: factorial(n - 1, n * acc);
}
}
/* no errors */
trampoline(factorial(100000));
32. Partial application
Process of breaking a single function into a multiple ones of smaller arity
Often conflated/mixed with currying witch is a process of breaking a function of
n arguments to a chain of n functions of 1 argument each
Motivation is that very often the functions obtained by supplying some but not
all of the required arguments
papply: (((a × b) → c) × a) → (b → c) = λ(f, x). λy. f (x, y)
curry: ((a × b) → c) → (a → (b → c)) = λf. λx. λy. f (x, y)
uncurry: (a → (b → c)) → ((a × b) → c) = λf. λ(x, y). f x y
33. Partial application example (simple & artificial)
// F#
let add1 x = x + 1
// traditional definition vs application
let add1 = (+) 1
let sortAsc = List.sortWith
(fun x y -> x - y)
let sortDsc = List.sortWith
(fun x y -> y - x)
// C#
Func<string, Claim> newClaim =
value => new Claim(
"group", value, "String", “sigma.software ");
/*…*/
var groups = RetrieveGroups();
// { "Management", "Accountants“ };
var groupClaims = groups.Select(newClaim);
34. Partial application (C#)
public class Package {
private readonly IContentReader contentReader;
private readonly IChecksumGenerator checksumGenerator;
private readonly IChecksumFormatter checksumFormatter;
public Package(
string name,
IContentReader contentReader,
IChecksumGenerator checksumGenerator,
IChecksumFormatter checksumFormatter) {
this.Name = name;
this.contentReader = contentReader;
this.checksumGenerator = checksumGenerator;
this.checksumFormatter = checksumFormatter; }
public string Name { get; }
public string GetChecksumStr(Encoding encoding) =>
this.checksumFormatter.Format(
this.checksumGenerator.Generate(contentReader.Read()),
encoding);
}
public interface IContentReader {
Stream Read();
}
public interface IChecksumGenerator {
byte[] Generate(Stream dataStream);
}
public interface IChecksumFormatter {
string Format(
byte[] checksum,
Encoding encoding);
}
36. Lazy evaluation
Lazy evaluation is a way of computing in which values are not calculated until
they are required
Lazy evaluation can be accomplished:
By language itself (Haskell)
Using special data structures
The opposite of lazy evaluation called eager evaluation
Application:
Infinite data structures
Memory footprint optimization
Startup speed optimization
Often combined with memorization
37. Lazy evaluation example (simple & artificial - F#)
// F#
let lazyVal = lazy (10 * 10)
lazyVal // Lazy<int> Value is not created.
lazyVal.Force() // 100
let nat = Seq.initInfinite(fun n -> n + 1)
// seq<int> = seq [1; 2; 3; 4; ...]
Seq.skip 10 nat |> Seq.take 10
// seq<int> = seq [11; 12; 13; 14; ...]
39. Pattern matching
Language feature that allows to choose the following instruction set based on
the match of given data with one of the declared patterns:
Constants
Predicates (functions that return Boolean values)
Data type
Anything else supported by a particular language
40. Pattern matching example
// F#
let ReadFromFile reader : StreamReader) =
match reader.ReadLine() with
| null -> printfn "n"; false
| line -> printfn "%s" line;
true
// C#
public static double ComputeArea (object shape)
{
switch (shape)
{
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Rectangle r:
return r.Height * r.Length;
default: throw new ArgumentException();
}
}
41. Concepts comparison
Goal OOP FP
Binding data & behavior Class -> Object Closure
Passing dependencies Constructor Partial application
Decoupling from concrete
implementation
Interface Function signature
Controlling execution flow Switch statement Pattern matching
42. Summary
Fundamentals concepts:
Immutability
Simplifies reasoning & testing; Increases scalability & reliability;
Purity
Simplifies reasoning and testing; Increases scalability & reliability;
First-class, higher-order functions
Closures
Functional way to bind data and behavior
Partial application
Functional way to have constructors
Lazy evaluation
Speed ups initialization
43. What’s next?
State management
The problem of state
Approaches of managing the state and side-effects
Continuations
Promises
Monads
FRP
44. Books
Structure and interpretation of computer programs
MIT course 6.037
Introduction to Functional Programming
Cambridge CS department
Hackers and painters
Google Play Books
Amazon
45. Articles
Rich Hickey – Simple made Easy
https://github.com/matthiasn/talk-
transcripts/blob/master/Hickey_Rich/SimpleMadeEasy.md
Vladimir Khorikov - Functional C#
https://github.com/vkhorikov/CSharpFunctionalExtensions
Robert Martin – FP vs OO
http://blog.cleancoder.com/uncle-bob/2014/11/24/FPvsOO.html
Why Functional Programming matters
https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf
Me - LISP: back to the future (a tribute to 60th anniversary):
https://sigma.software/about/media/lisp-back-future-tribute-60th-
anniversary
Любопытно что функциональная парадигма появилась так давно. Раньше чем структурное и чем GOTO стал considered harmful.
LISP – Первый функциональный и второй высокоуровневый язык –после FORTRAN, до сих пор наголову превосходит большинство языков общего назначения по своим возможностям.Первый язык с IF, garbage collection, функции как базовый тип данных, возможности метапрограммирования
Что интересно что Джон Маккарти (автор LISP и термина Искусственный интеллект) сам признавал что не понял работу Алонзо Чёрча
Выписал основные языки. Тут важно отметить 2 семейства которые повлияли на все остальные: LISP & ML
Интерес к древней функциональной парадигме, в значительной мере пропорционален изменению наших вычислительных возможностей.
Ядер становится всё больше при том что тактовая частота не сильно поменялась со средины нулевых
Заметим, что кроме центрального процессора мы имеет графический, который отличается наличием ТЫСЯЧ ядер, которые могут выполнять параллельные вычисления (пусть и узкоспециализированные)
И вот последний важный момент связанный с ограничениями железа, цена на оперативную память радикально (экспоненциально), аргумент против «более прожорливых» функциональных языков более не актуален.
Сразу к примеру, почему императивный подход оказался нерабочим в новых условиях
Каждая операция не зависит друг от друга, может выполнятся поралельно
Прежде чем перейти к основным понятиям функционального программирования интересно напомнить ОО
Прежде чем перейти к основным понятиям функционального программирования интересно напомнить ОО/ Мне кажется очень удобно подойти к этому вопросу также как мы подходим к ОО.
Здесть тоже можно выделить 4 принципа. При этом что важно все присущие ОО свойства кроме наследования остаются в силе и в рамках ФП
То что на первый взгляд кажется ограничением, в реальности крайне полезное свойство избавляющее разработчика от множества дефектов
Здесь возникает вопрос, не расточительно ли создавать каждый раз новые объекты, вместо того что бы изменять существующие?
Опять совершенное реальный пример, с реального проекта. Очень важная функция совершенно не тестируема, а результат её работы не воспроизводим.
Она явным образом совершает 2 вещи которые нарушают свойство её «чистоты»:
Ждёт сигналов от других процессов
Зависит от реального времени (DateTime.Now)
Кошерный код в данном контексте приобретает весьма определённое значение
Кошерный код в данном контексте приобретает весьма определённое значение
Помним, что в функциональный языках нельзя менять состояние объектов. По этому цикл for int i = 0; i<10;i++ не катит
Очень похоже на enumerator в C#, но здесь состояние только эмулируется. Всё это чистые функции
Элегантный костыль для языков с фиксированным стеком вызовов
Керрирование названо в честь Хаскеля Карри
Реальный пример, реального проекта. Чексумма вычисляется только при необходимости
Но причём тут частично применение?
Реальный пример, реального проекта. Чексумма вычисляется только при необходимости
Но причём тут частично применение?
Реальный пример, реального проекта. Чексумма вычисляется только при необходимости
Но причём тут частично применение?
Если всё так похоже почему так получилось что мы пользуем ООП?
Что важно. Это парадигма, это не конкретный язык. Как минимум принципам неизменяемости и чистоты вы можете следовать в любом языке
Introduction to Functional Programming (1996/7) - John Harrison