60
Игорь Кашкута Перспективы функционального подхода 2ГИС

7 кашкута

Embed Size (px)

Citation preview

Page 1: 7 кашкута

Игорь Кашкута

Перспективы функционального подхода

2ГИС

Page 2: 7 кашкута

Сложность

Проблема

Page 3: 7 кашкута

“Нужно стремиться к простоте”

Page 4: 7 кашкута

Системный подходк упрощению кода

Решение

Page 5: 7 кашкута

Идеи Неизменяемость Чистота Functional Reactive Programming

Page 6: 7 кашкута

НеизменяемостьImmutability

Page 7: 7 кашкута

@interface ContactModel : NSObject @property NSUInteger age; @property NSString *name; @property NSString *surname; @property NSArray *children; @property Organization *company; @end

Неизменяемость

Page 8: 7 кашкута

Неизменяемость

NSArray *contacts = LoadContactsFromDB(); ... ShowInGUI(contacts); ... AsyncBackupToCloud(contacts); ... AsyncFetchNewContacts(contacts); ... NSLog(@"%@", contacts);//??

Page 9: 7 кашкута

Неизменяемость

NSArray *contacts = LoadContactsFromDB(); ... NSLog(@"%@", contacts);//Original contacts NSLog(@"%@", contacts);//Contacts with anotherName

//In another thread [contacts[idx] setName:anotherName];

Page 10: 7 кашкута

• Состояние объекта нельзя изменить после его создания

• Мутация неизменяемых объектов — создание новых, Copy-On-Write

Неизменяемые объекты

Page 11: 7 кашкута

@interface ContactModel : NSObject @property (readonly) NSUInteger age; @property (readonly) NSString *name; @property (readonly) NSString *surname; @property (readonly) NSArray *children; @property (readonly) Organization *company; //Class methods for object creation @end

Неизменяемость

Page 12: 7 кашкута

Неизменяемость

//Copy-On-Write setter -(ContactModel *)cowSetAge:(NSUInteger)age { return [ContactModel modelWithAge:age name:self.name surname:self.surname children:self.children company:self.company]; }

Page 13: 7 кашкута

Неизменяемость

NSArray *contacts = LoadContactsFromDB(); ... ShowInGUI(contacts); ... AsyncBackupToCloud(contacts); ... AsyncFetchNewContacts(contacts); ... NSLog(@"%@", contacts);//Always the same!

[contacts[idx] setName:anotherName];//Error!

Page 14: 7 кашкута

• Потокобезопасность бесплатно

• Предсказуемость. Неизменяемые объекты всегда в консистентном состоянии

Неизменяемость

Page 15: 7 кашкута

ЧистотаPurity

Page 16: 7 кашкута

• Одинаковые аргументы — одинаковый результат

• Отсутствуют наблюдаемые сайд-эффекты

Чистые функции

Page 17: 7 кашкута

// Конкатенация строк — чистая функция NSString *CombineStrings(NSString *l, NSString *r);

// Любая математическая операция тоже чистая int sum(int a, int b);

@interface Collection : NSObject // Нечистая функция — нет аргументов и // возвращаемое значение каждый раз разное. - (id)next; @end

Чистые функции

Page 18: 7 кашкута

Нечистые функции порой удивляют и вызывают паранойю.

Нечистые функции

Page 19: 7 кашкута

• Простота тестирования • Предсказуемость

Чистые функции

Page 20: 7 кашкута

Functional Reactive Programming

Page 21: 7 кашкута

• Набор неизменяемых значений одного типа во времени

• Может закончится успешно или с ошибкой

• Может и вовсе не иметь значений

Поток

Page 22: 7 кашкута

• Создание потоков

• Преобразования одних в другие

• Подписка на значения

FRP Frameworks

Page 23: 7 кашкута

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 24: 7 кашкута

@“H”

Current text: H

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 25: 7 кашкута

@“H”

@“He”

Current text: H

Current text: He

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 26: 7 кашкута

@“H”

@“He”

@“Hel”Current text: H

Current text: He

Current text: Hel

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 27: 7 кашкута

@“H”

@“He”

@“Hel”

@“Hell”Current text: H

Current text: He

Current text: Hel

Current text: Hell

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 28: 7 кашкута

@“H”

@“He”

@“Hel”

@“Hell”

@“Hello”

Current text: H

Current text: He

Current text: Hel

Current text: Hell

Current text: Hello

UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];

Text Field как поток

Page 29: 7 кашкута

Button

Кнопка как поток

Page 30: 7 кашкута

Button

UIControlEventTouchDown

Кнопка как поток

Page 31: 7 кашкута

Button

UIControlEventTouchDown

UIControlEventTouchDragInside

Кнопка как поток

Page 32: 7 кашкута

Button

UIControlEventTouchDown

UIControlEventTouchDragInside

UIControlEventTouchDragExit

Кнопка как поток

Page 33: 7 кашкута

Button

UIControlEventTouchDown

UIControlEventTouchDragInside

UIControlEventTouchDragExit

UIControlEventTouchDragOutside

Кнопка как поток

Page 34: 7 кашкута

Button

UIControlEventTouchDown

UIControlEventTouchDragInside

UIControlEventTouchDragExit

UIControlEventTouchDragOutside

UIControlEventTouchDragEnter

Кнопка как поток

Page 35: 7 кашкута

Button

UIControlEventTouchDown

UIControlEventTouchDragInside

UIControlEventTouchDragExit

UIControlEventTouchDragOutside

UIControlEventTouchDragEnter

UIControlEventTouchUpInside

Кнопка как поток

Page 36: 7 кашкута

Дом

Автобус

Экспоцентр

Перчини

Дом

[locationManager.locationSignal subscribeNext:^(CLLocation *loc) { NSLog(@"New location: %@", loc); }];

Location Manager как поток

Page 37: 7 кашкута

request

result

request completed

[[APIClient fetchDataForUser:user] subscribeNext:^(NSData *result) { NSLog(@“Result: %@", result); } error:^(NSError *error) { NSLog(@"Error: %@", error); } completed:^{ NSLog(@"Request completed"); }];

Запрос в Сеть тоже поток

Page 38: 7 кашкута

error

request

[[APIClient fetchDataForUser:user] subscribeNext:^(NSData *result) { NSLog(@“Result: %@", result); } error:^(NSError *error) { NSLog(@"Error: %@", error); } completed:^{ NSLog(@"Request completed"); }];

Запрос в Сеть тоже поток

Page 39: 7 кашкута

• Разные с виду сущности можно представить в виде потока

• Поток представляет состояние — прошлое, настоящее, будущее

Поток

Page 40: 7 кашкута

• Комбинирование • Преобразование значений • Планирование выполнения на других тредах • И много всего другого!

Операции над потоками

Page 41: 7 кашкута

Комбинация потоков

Page 42: 7 кашкута

[[RACSignal merge:@[ [client fetchMyTweets], [client fetchTweetsForHashtag:@“codefest”] ]] subscribeNext:^(Tweet *newTweet){ //Показ newTweet в UI }];

Комбинация потоков

Page 43: 7 кашкута

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

Page 44: 7 кашкута

[[RACObserve(urlBarVM, text) map:^(NSString *urlFromUser) { return NormalizeURL(urlFromUser); }] subscribeNext:^(NSURL *url) { @strongify(self); [self.loadWebPageCommand execute:url]; }];

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

Page 45: 7 кашкута

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

Page 46: 7 кашкута

[[RACSignal combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] reduce:^(NSString *currentPassword, NSString *currentConfirmPassword) { return @([currentConfirmPassword isEqualToString: currentPassword]); }] subscribeNext:^(NSNumber *passwordsMatch) { @strongify(self); self.createButton.enabled = [passwordsMatch boolValue]; }];

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

Page 47: 7 кашкута

[[[[refreshButton.tapSignal deliverOn:RACScheduler.scheduler] map:^(id _){ NSLog(@“Fetching image in background thread..”); return SyncLoadImageFromNetwork(); }] deliverOnMainThread] subscribeNext:^(UIImage *img) { [self.imageView setImage:img]; }];

Планирование на другие треды

Page 48: 7 кашкута

Сайд-эффекты[[[[[refreshButton.tapSignal deliverOn:RACScheduler.scheduler] map:^(id _){ NSLog(@“Fetching image in background thread..”); return SyncLoadImageFromNetwork(); }] doNext:^(UIImage *img){ SaveImageToDisk(img); }] deliverOnMainThread] subscribeNext:^(UIImage *img) { [self.imageView setImage:img]; }];

Page 49: 7 кашкута

Реактивное присваивание

//Неважно, как именно начался поиск, но после его //начала фокус с поисковой строки надо убрать. RAC(self, topBarVM.textFieldVM.focused) = [self.searchVM.searchDidStartSignal mapReplace:@NO];

Page 50: 7 кашкута

• В четыре раза лучше коллбэков • В два раза лучше промисов • Способствуют локальности кода • Упрощают обработку ошибок в цепочках • Избавляют от Callback Hell

Потоки — это монады

Page 51: 7 кашкута

[task1 setCompleted:^(id result1){ [task2 setCompleted:^(id result2){ [task3 setCompleted^(id result3){ [task4 setCompleted:^(id result4){ [task5 setCompleted:^(id result5){ //Сделать что-то с result5 NSLog(@"Ура! %@", result5); }]; [task5 start]; }]; [task4 start]; }]; [task3 start]; }]; [task2 start]; }]; [task1 start];

Callback Hell

Page 52: 7 кашкута

Оператор FlatMap

Page 53: 7 кашкута

[[[[[[client fetchTask1] flattenMap:^(id result1) { return [client fetchTask2]; }] flattenMap:^(id result2) { return [client fetchTask3]; }] flattenMap:^(id result3) { return [client fetchTask4]; }] flattenMap:^(id result4) { return [client fetchTask5]; }] subscribeNext:^(id result5){ NSLog(@“Ура! %@”, result5); } error:^(NSError *error){ //Do error processing for any task }];

Оператор FlatMap

Page 54: 7 кашкута

• Неизменяемые объекты — значения в потоке • Потоки изолируют состояние • Операторы — чистые функции • Операторы изолируют взаимосвязи

Functional Reactive Programming

Page 55: 7 кашкута

• Прозрачность кода • Единообразие в работе с разными сущностями • Простота асинхронного программирования • Описание “что” надо сделать, вместо “как”

FRP на практике

Page 56: 7 кашкута

• Память/производительность • Большой стек вызовов • Трудно построчно отлаживать • Нет готовых специалистов

Цена

Page 57: 7 кашкута

Повторим Неизменяемость Чистота Functional Reactive Programming

Page 58: 7 кашкута

Всё это доступно вам уже сейчас!

Page 60: 7 кашкута

Спасибо!

@ikashkutaИгорь Кашкута[email protected]