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

  • View
    580

  • Download
    0

  • Category

    Mobile

Preview:

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

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

Спасибо!

@ikashkutaИгорь Кашкутаi.kashkuta@2gis.ru

Recommended