Upload
igor-kashkuta
View
559
Download
0
Embed Size (px)
Citation preview
Игорь Кашкута
Перспективы функционального подхода
2ГИС
Сложность
Проблема
“Нужно стремиться к простоте”
Системный подходк упрощению кода
Решение
Идеи Неизменяемость Чистота Functional Reactive Programming
НеизменяемостьImmutability
@interface ContactModel : NSObject @property NSUInteger age; @property NSString *name; @property NSString *surname; @property NSArray *children; @property Organization *company; @end
Неизменяемость
Неизменяемость
NSArray *contacts = LoadContactsFromDB(); ... ShowInGUI(contacts); ... AsyncBackupToCloud(contacts); ... AsyncFetchNewContacts(contacts); ... NSLog(@"%@", contacts);//??
Неизменяемость
NSArray *contacts = LoadContactsFromDB(); ... NSLog(@"%@", contacts);//Original contacts NSLog(@"%@", contacts);//Contacts with anotherName
//In another thread [contacts[idx] setName:anotherName];
• Состояние объекта нельзя изменить после его создания
• Мутация неизменяемых объектов — создание новых, Copy-On-Write
Неизменяемые объекты
@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
Неизменяемость
Неизменяемость
//Copy-On-Write setter -(ContactModel *)cowSetAge:(NSUInteger)age { return [ContactModel modelWithAge:age name:self.name surname:self.surname children:self.children company:self.company]; }
Неизменяемость
NSArray *contacts = LoadContactsFromDB(); ... ShowInGUI(contacts); ... AsyncBackupToCloud(contacts); ... AsyncFetchNewContacts(contacts); ... NSLog(@"%@", contacts);//Always the same!
[contacts[idx] setName:anotherName];//Error!
• Потокобезопасность бесплатно
• Предсказуемость. Неизменяемые объекты всегда в консистентном состоянии
Неизменяемость
ЧистотаPurity
• Одинаковые аргументы — одинаковый результат
• Отсутствуют наблюдаемые сайд-эффекты
Чистые функции
// Конкатенация строк — чистая функция NSString *CombineStrings(NSString *l, NSString *r);
// Любая математическая операция тоже чистая int sum(int a, int b);
@interface Collection : NSObject // Нечистая функция — нет аргументов и // возвращаемое значение каждый раз разное. - (id)next; @end
Чистые функции
Нечистые функции порой удивляют и вызывают паранойю.
Нечистые функции
• Простота тестирования • Предсказуемость
Чистые функции
Functional Reactive Programming
• Набор неизменяемых значений одного типа во времени
• Может закончится успешно или с ошибкой
• Может и вовсе не иметь значений
Поток
• Создание потоков
• Преобразования одних в другие
• Подписка на значения
FRP Frameworks
UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];
Text Field как поток
@“H”
Current text: H
UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];
Text Field как поток
@“H”
@“He”
Current text: H
Current text: He
UITextField *field = [UITextField new]; [field.rac_textSignal subscribeNext:^(NSString *text) { NSLog(@"Current text: %@", text); }];
Text Field как поток
@“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 как поток
@“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 как поток
@“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 как поток
Button
Кнопка как поток
Button
UIControlEventTouchDown
Кнопка как поток
Button
UIControlEventTouchDown
UIControlEventTouchDragInside
Кнопка как поток
Button
UIControlEventTouchDown
UIControlEventTouchDragInside
UIControlEventTouchDragExit
Кнопка как поток
Button
UIControlEventTouchDown
UIControlEventTouchDragInside
UIControlEventTouchDragExit
UIControlEventTouchDragOutside
Кнопка как поток
Button
UIControlEventTouchDown
UIControlEventTouchDragInside
UIControlEventTouchDragExit
UIControlEventTouchDragOutside
UIControlEventTouchDragEnter
Кнопка как поток
Button
UIControlEventTouchDown
UIControlEventTouchDragInside
UIControlEventTouchDragExit
UIControlEventTouchDragOutside
UIControlEventTouchDragEnter
UIControlEventTouchUpInside
Кнопка как поток
Дом
Автобус
Экспоцентр
Перчини
Дом
[locationManager.locationSignal subscribeNext:^(CLLocation *loc) { NSLog(@"New location: %@", loc); }];
Location Manager как поток
request
result
request completed
[[APIClient fetchDataForUser:user] subscribeNext:^(NSData *result) { NSLog(@“Result: %@", result); } error:^(NSError *error) { NSLog(@"Error: %@", error); } completed:^{ NSLog(@"Request completed"); }];
Запрос в Сеть тоже поток
error
request
[[APIClient fetchDataForUser:user] subscribeNext:^(NSData *result) { NSLog(@“Result: %@", result); } error:^(NSError *error) { NSLog(@"Error: %@", error); } completed:^{ NSLog(@"Request completed"); }];
Запрос в Сеть тоже поток
• Разные с виду сущности можно представить в виде потока
• Поток представляет состояние — прошлое, настоящее, будущее
Поток
• Комбинирование • Преобразование значений • Планирование выполнения на других тредах • И много всего другого!
Операции над потоками
Комбинация потоков
[[RACSignal merge:@[ [client fetchMyTweets], [client fetchTweetsForHashtag:@“codefest”] ]] subscribeNext:^(Tweet *newTweet){ //Показ newTweet в UI }];
Комбинация потоков
Преобразование значений
[[RACObserve(urlBarVM, text) map:^(NSString *urlFromUser) { return NormalizeURL(urlFromUser); }] subscribeNext:^(NSURL *url) { @strongify(self); [self.loadWebPageCommand execute:url]; }];
Преобразование значений
Преобразование значений
[[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]; }];
Преобразование значений
[[[[refreshButton.tapSignal deliverOn:RACScheduler.scheduler] map:^(id _){ NSLog(@“Fetching image in background thread..”); return SyncLoadImageFromNetwork(); }] deliverOnMainThread] subscribeNext:^(UIImage *img) { [self.imageView setImage:img]; }];
Планирование на другие треды
Сайд-эффекты[[[[[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]; }];
Реактивное присваивание
//Неважно, как именно начался поиск, но после его //начала фокус с поисковой строки надо убрать. RAC(self, topBarVM.textFieldVM.focused) = [self.searchVM.searchDidStartSignal mapReplace:@NO];
• В четыре раза лучше коллбэков • В два раза лучше промисов • Способствуют локальности кода • Упрощают обработку ошибок в цепочках • Избавляют от Callback Hell
Потоки — это монады
[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
Оператор FlatMap
[[[[[[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
• Неизменяемые объекты — значения в потоке • Потоки изолируют состояние • Операторы — чистые функции • Операторы изолируют взаимосвязи
Functional Reactive Programming
• Прозрачность кода • Единообразие в работе с разными сущностями • Простота асинхронного программирования • Описание “что” надо сделать, вместо “как”
FRP на практике
• Память/производительность • Большой стек вызовов • Трудно построчно отлаживать • Нет готовых специалистов
Цена
Повторим Неизменяемость Чистота Functional Reactive Programming
Всё это доступно вам уже сейчас!
Что дальше The Reactive Manifesto ReactiveCocoa/RxJava/Rx Clojure/Rich Hickey Haskell
Спасибо!
@ikashkutaИгорь Кашкута[email protected]