كيفية العمل مع استفسارات متعددة. تكوين ، المخفض ، FP

مرحبا يا هبر. اسمي مكسيم ، أنا مطور iOS في FINCH. اليوم سأريك بعض الممارسات لاستخدام البرمجة الوظيفية التي قمنا بتطويرها في قسمنا.

أريد أن أشير على الفور إلى أنني لا أحثك ​​على استخدام البرمجة الوظيفية في كل مكان - هذه ليست حلا سحريا لجميع المشاكل. لكن يبدو لي ، في بعض الحالات ، أن FP يمكن أن توفر الحلول الأكثر مرونة وأنيقة للمشاكل غير القياسية.

FP مفهوم شائع ، لذلك لن أشرح الأساسيات. أنا متأكد من أنك تستخدم بالفعل الخريطة ، والحد ، وضغط الخريطة ، أولاً (حيث :) والتقنيات المشابهة في مشاريعك. سوف تركز المقالة على حل مشكلة الاستعلامات المتعددة والعمل مع المخفض.

مشكلة استعلام متعددة


أنا أعمل في مجال الاستعانة بمصادر خارجية للإنتاج ، وهناك مواقف عندما يتولى العميل مع المتعاقدين معه من الباطن إنشاء خلفية. هذا أبعد ما يكون عن الواجهة الخلفية الأكثر ملاءمة وعليك تقديم استفسارات متعددة ومتوازية.

أحيانًا يمكنني كتابة شيء مثل:

networkClient.sendRequest(request1) { result in switch result { case .success(let response1): // ... self.networkClient.sendRequest(request2) { result in // ... switch result { case .success(let response2): // ...  -     response self.networkClient.sendRequest(request3) { result in switch result { case .success(let response3): // ...  -     completion(Result.success(response3)) case .failure(let error): completion(Result.failure(.description(error))) } } case .failure(let error): completionHandler(Result.failure(.description(error))) } } case .failure(let error): completionHandler(Result.failure(.description(error))) } } 

مثير للاشمئزاز ، أليس كذلك؟ ولكن هذا هو الواقع الذي كنت بحاجة للعمل فيه.

كنت بحاجة لإرسال ثلاثة طلبات متتالية للحصول على إذن. أثناء إعادة التجهيز ، اعتقدت أنه سيكون من الجيد تقسيم كل طلب إلى طرق منفصلة ودعوتهم إلى الانتهاء ، وبالتالي تفريغ طريقة واحدة ضخمة. اتضح شيء مثل:

 func obtainUserStatus(completion: @escaping (Result<AuthResponse>) -> Void) { let endpoint= AuthEndpoint.loginRoute networkService.request(endpoint: endpoint, cachingEnabled: false) { [weak self] (result: Result<LoginRouteResponse>) in switch result { case .success(let response): self?.obtainLoginResponse(response: response, completion: completion) case .failure(let error): completion(.failure(error)) } } } private func obtainLoginResponse(_ response: LoginRouteResponse, completion: @escaping (Result<AuthResponse>) -> Void) { let endpoint= AuthEndpoint.login networkService.request(endpoint: endpoint, cachingEnabled: false) { [weak self] (result: Result<LoginResponse>) in switch result { case .success(let response): self?.obtainAuthResponse(response: response, completion: completion) case .failure(let error): completion(.failure(error)) } } private func obtainAuthResponse(_ response: LoginResponse, completion: @escaping (Result<AuthResponse>) -> Void) { let endpoint= AuthEndpoint.auth networkService.request(endpoint: endpoint, cachingEnabled: false) { (result: Result<AuthResponse>) in completion(result) } } 

يمكن أن نرى أنه في كل من الطرق الخاصة لا بد لي من الوكيل

 completion: @escaping (Result<AuthResponse>) -> Void 

وأنا لا أحب ذلك حقًا.

ثم جاء التفكير إلى ذهني - "لماذا لا تلجأ إلى البرمجة الوظيفية؟" بالإضافة إلى ذلك ، فإن سويفت ، بسحره السحري والنحوي ، يجعل من الممكن تقسيم الشفرة إلى عناصر فردية بطريقة مثيرة للاهتمام وهضم.

تكوين و المخفض


ترتبط البرمجة الوظيفية ارتباطًا وثيقًا بمفهوم التكوين - الخلط ، والجمع بين شيء ما. في البرمجة الوظيفية ، تشير التركيبة إلى أننا نجمع بين السلوك من الكتل الفردية ، ومن ثم ، في المستقبل ، نعمل معها.

التكوين من وجهة نظر رياضية شيء مثل:

 func compose<A,B,C>(_ f: @escaping (A) -> B, and g: @escaping (B) -> C) -> (A) -> C { return { a in g(f(a)) } } 

هناك وظائف f و g تحدد داخليًا معلمات الإخراج والإدخال. نريد الحصول على نوع من السلوك الناتج من طرق الإدخال هذه.

على سبيل المثال ، يمكنك عمل إغلاقين ، أحدهما يزيد من رقم الإدخال بمقدار 1 ، والثاني يضاعف من تلقاء نفسه.

 let increment: (Int) -> Int = { value in return value + 1 } let multiply: (Int) -> Int = { value in return value * value } 

نتيجة لذلك ، نريد تطبيق كلتا العمليتين:

 let result = compose(multiply, and: increment) result(10) //     101 


لسوء الحظ المثال الخاص بي ليس النقابي
(إذا قمنا بتبادل الزيادة والضرب ، فسنحصل على الرقم 121) ، ولكن الآن دعنا نتجاهل هذه اللحظة.

 let result = compose(increment, and: multiply) result(10) //     121 

ملاحظة: أحاول بشكل خاص أن أجعل الأمثلة أبسط حتى تكون واضحة قدر الإمكان)

في الممارسة العملية ، تحتاج غالبًا إلى فعل شيء مثل هذا:

 let value: Int? = array .lazy .filter { $0 % 2 == 1 } .first(where: { $0 > 10 }) 

هذا هو التكوين. وضعنا الإجراء الإدخال والحصول على بعض تأثير الإخراج. لكن هذه ليست مجرد إضافة بعض الأشياء - إنها إضافة سلوك كامل.

والآن دعونا نفكر بشكل أكثر تجريدية :)


في طلبنا ، لدينا نوع من الحالة. قد تكون هذه هي الشاشة التي يراها المستخدم حاليًا أو البيانات الحالية المخزنة في التطبيق ، إلخ.
بالإضافة إلى ذلك ، لدينا إجراء - هذا هو الإجراء الذي يمكن للمستخدم القيام به (انقر فوق الزر ، وانتقل خلال المجموعة ، وأغلق التطبيق ، إلخ). ونتيجة لذلك ، فإننا نعمل على هذين المفهومين ونربطهما ببعضهما البعض ، أي أننا نجمع ، هممم ، نجمع (في مكان سمعت عنه بالفعل).

ولكن ماذا لو أنشأت كيانًا يجمع بين حالتي وأفعالي معًا؟

لذلك نحن نحصل على المخفض

 struct Reducer<S, A> { let reduce: (S, A) -> S } 

سنعطي الحالة الحالية والإجراء لتخفيض طريقة إدخال المدخلات ، وفي المخرجات سنحصل على حالة جديدة ، والتي تم تشكيلها في الداخل.

يمكننا وصف هذه البنية بعدة طرق: من خلال تحديد حالة جديدة أو استخدام طريقة وظيفية أو استخدام نماذج قابلة للتغيير.

 struct Reducer<S, A> { let reduce: (S, A) -> S } struct Reducer<S, A> { let reduce: (S) -> (A) -> S } struct Reducer<S, A> { let reduce: (inout S, A) -> Void } 

الخيار الأول هو "الكلاسيكية".

والثاني هو أكثر وظيفية. النقطة المهمة هي أننا لا نرجع الحالة ، ولكن الطريقة التي تتخذ إجراءً ، والتي بدورها تُرجع الحالة بالفعل. هذا هو في الأساس الكاري من طريقة الحد.

الخيار الثالث هو العمل مع الدولة حسب المرجع. مع هذا النهج ، نحن لا نصدر الحالة فقط ، بل نعمل مع الإشارة إلى الكائن الذي يأتي. يبدو لي أن هذه الطريقة ليست جيدة جدًا ، لأن هذه النماذج (القابلة للتغيير) سيئة. من الأفضل إعادة إنشاء الحالة الجديدة (المثيل) وإعادتها. ولكن من أجل بساطة وإظهار أمثلة أخرى ، فإننا نوافق على استخدام الخيار الأخير.

تطبيق المخفض


نحن نطبق مفهوم المخفض على الكود الموجود - قم بإنشاء RequestState ، ثم قم بتهيئته وضبطه.

 class RequestState { // MARK: - Private properties private let semaphore = DispatchSemaphore(value: 0) private let networkClient: NetworkClient = NetworkClientImp() // MARK: - Public methods func sendRequest<Response: Codable>(_ request: RequestProtocol, completion: ((Result<Response>) -> Void)?) { networkClient.sendRequest(request) { (result: Result<Response>) in completion?(result) self.semaphore.signal() } semaphore.wait() } } 

لمزامنة طلبات أضفت DispatchSemaphore

المضي قدما. نحن الآن بحاجة إلى إنشاء RequestAction مع ، على سبيل المثال ، ثلاثة طلبات.

 enum RequestAction { case sendFirstRequest(FirstRequest) case sendSecondRequest(SecondRequest) case sendThirdRequest(ThirdRequest) } 

الآن إنشاء المخفض الذي يحتوي على RequestState و RequestAction. نضع السلوك - ماذا نريد أن نفعله مع الطلب الأول والثاني والثالث.

 let requestReducer = Reducer<RequestState, RequestAction> { state, action in switch action { case .sendFirstRequest(let request): state.sendRequest(request) { (result: Result<FirstResponse>) in // 1 Response } case .sendSecondRequest(let request): state.sendRequest(request) { (result: Result<SecondResponse>) in // 2 Response } case .sendThirdRequest(let request): state.sendRequest(request) { (result: Result<ThirdResponse>) in // 3 Response } } } 

في النهاية ، نحن نسمي هذه الأساليب. لقد اتضح أن أسلوب التصريح أكثر ، حيث من الواضح أن الطلبات الأولى والثانية والثالثة قادمة. كل شيء مقروء وواضح.

 var state = RequestState() requestReducer.reduce(&state, .sendFirstRequest(FirstRequest())) requestReducer.reduce(&state, .sendSecondRequest(SecondRequest())) requestReducer.reduce(&state, .sendThirdRequest(ThirdRequest())) 

استنتاج


لا تخف من تعلم أشياء جديدة ولا تخف من تعلم البرمجة الوظيفية. أعتقد أن أفضل الممارسات في مفترق طرق التكنولوجيا. حاول الجمع بين نماذج البرمجة المختلفة والاستفادة منها بشكل أفضل.

إذا كان هناك أي مهمة غير تافهة ، فمن المنطقي أن ننظر إليها من زاوية مختلفة.

Source: https://habr.com/ru/post/ar455840/


All Articles