• برنامه نویسی واکنشی سی شارپ. ببینید «برنامه‌نویسی واکنش‌گرا» در فرهنگ‌های دیگر چیست. اجزای اصلی کاکائو واکنشی

    دنیای توسعه OOP به طور کلی و زبان جاوا به طور خاص زندگی بسیار فعالی دارند. این روند مد خاص خود را دارد و امروز یکی از روندهای اصلی فصل - چارچوب ReactiveX را تجزیه و تحلیل خواهیم کرد. اگر هنوز از این موج دور هستید - قول می دهم که آن را دوست خواهید داشت! قطعا بهتر از شلوار جین کمر بلند است :).

    برنامه نویسی واکنشی

    به محض اینکه زبان‌های OOP برای استفاده انبوه به بلوغ رسیدند، توسعه‌دهندگان متوجه شدند که گاهی اوقات تا چه اندازه قابلیت‌های زبان‌های شبیه C وجود ندارد. از آنجایی که نوشتن کد به سبک برنامه نویسی عملکردی به طور جدی کیفیت کد OOP را از بین می برد، و از این رو قابلیت نگهداری پروژه، یک ترکیبی اختراع شد - برنامه نویسی واکنشی.

    پارادایم توسعه واکنشی مبتنی بر ایده ردیابی مداوم تغییرات در وضعیت یک شی است. اگر چنین تغییراتی رخ داده باشد، تمام اشیاء علاقه مند باید داده های به روز شده را دریافت کنند و فقط با آنها کار کنند و موارد قدیمی را فراموش کنند.

    یک مثال خوب از یک ایده برنامه نویسی واکنشی، صفحه گسترده اکسل است. اگر چندین سلول را با یک فرمول پیوند دهید، هر بار که داده‌های این سلول‌ها تغییر می‌کنند، نتیجه محاسبه تغییر می‌کند. برای حسابداری، چنین تغییر پویا در داده ها یک چیز رایج است، اما برای برنامه نویسان این یک استثنا است.

    A=3; b=4; c=a+b; F1 (c)؛ a=1; F2 (c)؛

    در این مثال، توابع F1 و F2 با مقادیر مختلف متغیر C کار می کنند. اغلب لازم است که هر دو تابع فقط به روزترین داده ها را داشته باشند - برنامه نویسی واکنشی به شما این امکان را می دهد که فوراً F1 را با موارد جدید فراخوانی کنید. پارامترها بدون تغییر منطق خود توابع. این ساختار کد به برنامه امکان پاسخگویی فوری به هر تغییری را می دهد که آن را سریع، انعطاف پذیر و پاسخگو می کند.

    ReactiveX

    پیاده سازی ایده های برنامه نویسی واکنشی از ابتدا می تواند بسیار مشکل ساز باشد - مشکلاتی وجود دارد و زمان مناسبی را می طلبد. بنابراین، برای بسیاری از توسعه دهندگان، این پارادایم تا زمانی که ReactiveX ظاهر شد، فقط به عنوان یک ماده نظری باقی ماند.

    چارچوب ReactiveX یک ابزار برنامه نویسی واکنشی است که با تمام زبان های محبوب OOP کار می کند. خود سازندگان آن را API چند پلتفرمی برای توسعه ناهمزمان، بر اساس الگوی Observer می نامند.

    اگر اصطلاح "برنامه نویسی واکنشی" نوعی مدل تئوریک باشد، الگوی Observer مکانیزمی آماده برای ردیابی تغییرات در یک برنامه است. و شما باید اغلب آنها را ردیابی کنید: بارگیری و به روز رسانی داده ها، اعلان های رویداد و غیره.

    الگوی Observer تقریباً به اندازه خود OOP وجود داشته است. شیئی که حالت آن می تواند تغییر کند، ناشر نامیده می شود (ترجمه ای رایج از عبارت Observable). همه شرکت کنندگان دیگری که به این تغییرات علاقه مند هستند مشترک هستند (Observer, Subscriber). برای دریافت اعلان ها، مشترکین با ذکر صریح شناسه خود در ناشر ثبت نام می کنند. ناشر هر از گاهی اعلان هایی تولید می کند که توسط ناشر به لیست مشترکین ثبت شده ارسال می شود.

    در واقع، سازندگان ReactiveX به هیچ چیز انقلابی نرسیدند، آنها فقط به راحتی این الگو را پیاده سازی کردند. و اگرچه بسیاری از زبان‌های OOP، و به‌ویژه جاوا، پیاده‌سازی‌های آماده‌ای از الگو دارند، این چارچوب دارای «تنظیم» اضافی است که «Observer» را به یک ابزار بسیار قدرتمند تبدیل می‌کند.

    RxAndroid

    پورت کتابخانه ReactiveX برای دنیای اندروید rxAndroid نام دارد و مانند همیشه از طریق Gradle متصل می شود.

    کامپایل "io.reactivex:rxandroid:1.1.0"

    ناشر تولید کننده اعلان ها در اینجا با استفاده از کلاس Observable مشخص می شود. یک ناشر می تواند چندین مشترک داشته باشد که برای پیاده سازی آنها از کلاس Subscriber استفاده می کنیم. رفتار پیش‌فرض برای یک Observable این است که یک یا چند پیام را برای مشترکین ارسال کند و سپس از آن خارج شود یا یک پیام خطا صادر کند. پیام ها می توانند هم متغیر و هم اشیاء عدد صحیح باشند.

    Rx.Observable myObserv = rx.Observable.create(rx.Observable.OnSubscribe جدید () ( @Override public void call(Subscribersubscriber) ( subscriber.onNext("سلام"); subscriber.onNext("world"); subscriber.onCompleted(); ) ));

    در این حالت، ناشر myObserv ابتدا رشته های hello و پیام و سپس پیام موفقیت آمیز را ارسال می کند. ناشر می تواند متدهای onNext() ، onCompleted() و ()onEror را فراخوانی کند، بنابراین مشترکین باید آنها را تعریف کنند.

    مشترک mySub = مشترک جدید () (... @Override public void onNext(String value) (Log.e("got data", " " + value);) );

    همه چیز برای کار آماده است. باقی مانده است که اشیاء را به یکدیگر متصل کنیم - و "سلام، جهان!" در برنامه نویسی واکنشی آماده است!

    MyObserv.subscribe(mySub);

    باید بگویم که این یک مثال بسیار ساده بود. ReactiveX گزینه های زیادی برای رفتار همه شرکت کنندگان در الگو دارد: فیلتر کردن، گروه بندی، مدیریت خطا. مزایای برنامه نویسی واکنشی را فقط می توان با آزمایش در عمل احساس کرد. بیایید با جدیت بیشتری به کار بپردازیم.

    ادامه فقط برای اعضا در دسترس است

    گزینه 1. برای خواندن تمام مطالب سایت به انجمن "سایت" بپیوندید

    عضویت در انجمن در طول دوره مشخص شده به شما امکان می دهد به همه مطالب هکر دسترسی داشته باشید، تخفیف تجمعی شخصی شما را افزایش می دهد و به شما امکان می دهد امتیاز حرفه ای Xakep را جمع آوری کنید!

    با گذشت زمان، زبان های برنامه نویسی به دلیل ظهور فناوری های جدید، نیازهای مدرن یا تمایل ساده به تجدید سبک نوشتن کد، دائماً در حال تغییر و تحول هستند. برنامه نویسی واکنشی را می توان با استفاده از چارچوب های مختلفی مانند Reactive Cocoa پیاده سازی کرد. دامنه سبک دستوری زبان Objective-C را تغییر می دهد و این رویکرد به برنامه نویسی چیزهای زیادی برای ارائه پارادایم استاندارد دارد. این البته توجه توسعه دهندگان iOS را به خود جلب می کند.

    ReactiveCocoa سبک اظهاری را به Objective-C می آورد. منظور ما از این چیست؟ سبک دستوری سنتی که توسط زبان هایی مانند C، C++، Objective-C و Java و غیره استفاده می شود را می توان به صورت زیر توصیف کرد: شما دستورالعمل هایی را برای یک برنامه کامپیوتری می نویسید که باید به روش خاصی اجرا شود. به عبارت دیگر، شما می گویید "چگونه کاری را انجام دهیم". در حالی که برنامه نویسی اعلانی به شما اجازه می دهد تا جریان کنترل را به عنوان دنباله ای از اقدامات، "چه باید کرد"، بدون تعریف "چگونگی انجام آن" توصیف کنید.

    برنامه نویسی امری در مقابل عملکردی

    رویکرد ضروری به برنامه نویسی شامل شرح مفصلی از هر مرحله است که رایانه باید برای تکمیل وظایف انجام دهد. در واقع، سبک امری در زبان های برنامه نویسی بومی استفاده می شود (یا هنگام نوشتن کد ماشین استفاده می شود). به هر حال، این ویژگی مشخصه اکثر زبان های برنامه نویسی است.

    در مقابل، رویکرد عملکردی مشکلات را با مجموعه ای از عملکردهایی که باید انجام شوند حل می کند. شما پارامترهای ورودی را برای هر تابع تعریف می کنید و هر تابع چه چیزی را برمی گرداند. این دو رویکرد برنامه نویسی بسیار متفاوت هستند.

    در اینجا تفاوت های اصلی زبان وجود دارد:

    1. تغییر حالت

    برای برنامه نویسی کاربردی خالص، هیچ تغییر حالتی وجود ندارد زیرا عوارض جانبی وجود ندارد. یک عارضه جانبی شامل تغییرات حالت علاوه بر مقدار بازگشتی به دلیل برخی تعاملات خارجی است. SR (شفافیت ارجاعی) یک عبارت فرعی اغلب به عنوان "عدم عوارض جانبی" تعریف می شود و در درجه اول به عملکردهای خالص اشاره دارد. SP به اجرای تابع اجازه دسترسی خارجی به حالت فرار تابع را نمی دهد، زیرا هر عبارت فرعی بنا به تعریف یک فراخوانی تابع است.

    برای روشن شدن موضوع، توابع خالص دارای ویژگی های زیر هستند:

    • تنها خروجی قابل توجه مقدار بازگشتی است
    • تنها وابستگی پارامترهای ورودی آرگومان ها هستند
    • آرگومان ها قبل از تولید هر خروجی به طور کامل مشخص می شوند

    علیرغم این واقعیت که رویکرد عملکردی عوارض جانبی را به حداقل می رساند، نمی توان به طور کامل از آنها اجتناب کرد، زیرا آنها بخشی ذاتی هر توسعه هستند.

    از سوی دیگر، توابع در برنامه نویسی امری شفافیت ارجاعی ندارند و این ممکن است تنها تفاوت بین رویکرد اعلامی و امری باشد. عوارض جانبی به طور گسترده برای پیاده سازی حالت و I/O استفاده می شود. دستورات در زبان مبدأ می‌توانند حالت را تغییر دهند و در نتیجه مقادیر متفاوتی برای عبارت همان زبان ایجاد کنند.

    ReactiveCocoa چطور؟ این یک چارچوب کاربردی برای Objective-C است، که از نظر مفهومی یک زبان ضروری است، بدون اینکه توابع کاملاً خالص را شامل شود. هنگام تلاش برای جلوگیری از تغییر حالت، عوارض جانبی محدود نمی شود.

    2. اشیاء درجه یک

    در برنامه نویسی تابعی، اشیا و توابعی وجود دارند که اشیاء درجه یک هستند. چه مفهومی داره؟ این بدان معنی است که توابع را می توان به عنوان یک پارامتر ارسال کرد، به یک متغیر اختصاص داد یا از یک تابع برگرداند. چرا راحت است؟ این امر مدیریت بلوک‌های اجرا، ایجاد و ترکیب توابع را به روش‌های مختلف بدون دردسر نشانگرهای تابع آسان می‌کند (char *(*(**foo)()؛ - لذت ببرید!).

    زبان‌هایی که از رویکرد امری استفاده می‌کنند، با توجه به عبارات درجه یک، ویژگی‌های خاص خود را دارند. Objective-C چطور؟ دارای بلوک هایی به عنوان اجرای بسته شدن است. توابع مرتبه بالاتر (HFOs) را می توان با در نظر گرفتن بلوک ها به عنوان پارامتر مدل کرد. در این حالت، بلوک یک بسته است و یک تابع مرتبه بالاتر را می توان از مجموعه خاصی از بلوک ها ایجاد کرد.

    با این حال، فرآیند دستکاری FVP در زبان های تابعی سریعتر است و به خطوط کد کمتری نیاز دارد.

    3. کنترل جریان اصلی

    حلقه های سبک امری به عنوان فراخوانی تابع بازگشت در برنامه نویسی تابعی نشان داده می شوند. تکرار در زبان های تابعی معمولاً از طریق بازگشت انجام می شود. چرا؟ احتمالا به خاطر پیچیدگی. برای توسعه‌دهندگان Objective-C، حلقه‌ها بسیار مناسب‌تر به نظر می‌رسند. بازگشت‌ها می‌توانند مشکلاتی مانند مصرف بیش از حد RAM ایجاد کنند.

    ولی! ما می توانیم یک تابع را بدون استفاده از حلقه یا بازگشت بنویسیم. برای هر یک از اقدامات تخصصی بی نهایت ممکن که می تواند برای هر عنصر از یک مجموعه اعمال شود، برنامه نویسی تابعی از توابع تکرار شونده قابل استفاده مجدد استفاده می کند. نقشه”, “تا کردن”, “". این توابع برای بازسازی کد منبع مفید هستند. آنها تکرار را کاهش می دهند و نیازی به نوشتن یک تابع جداگانه ندارند. (ادامه مطلب را بخوانید، ما اطلاعات بیشتری در مورد آن داریم!)

    4. دستور اجرا

    عبارات اعلانی فقط روابط منطقی آرگومان های تابع زیرعنوان و روابط حالت پایدار را نشان می دهند. بنابراین در غیاب عوارض جانبی، انتقال حالت هر فراخوانی تابع مستقل از بقیه اتفاق می افتد.

    ترتیب عملکردی اجرای عبارات امری به حالت فرار بستگی دارد. بنابراین، ترتیب اجرا مهم است و به طور ضمنی توسط سازمان کد منبع تعیین می شود. در این سوال می توان به تفاوت راهبردهای ارزیابی هر دو رویکرد اشاره کرد.

    ارزیابی‌های تنبل یا فراخوانی، استراتژی‌هایی در زبان‌های برنامه‌نویسی کاربردی هستند. در چنین حالتی، ارزیابی عبارت تا زمانی که مقدار آن مورد نیاز باشد به تعویق می‌افتد، در نتیجه از ارزیابی‌های مکرر اجتناب می‌کنیم. به عبارت دیگر، عبارات تنها زمانی ارزیابی می شوند که عبارت وابسته ارزیابی شود. ترتیب عملیات نامشخص می شود.

    در مقابل، ارزیابی شدید در یک زبان امری به این معنی است که عبارت به محض اینکه به یک متغیر متصل شود، ارزیابی خواهد شد. این امر مستلزم دیکته کردن ترتیب اجراست.بنابراین، تعیین زمان ارزیابی عبارات فرعی (از جمله توابع) آسان تر است، زیرا عبارات فرعی می توانند عوارض جانبی داشته باشند که بر ارزیابی عبارات دیگر تأثیر می گذارد.

    5. تعداد کد

    این مهم است، رویکرد کاربردی نیاز به نوشتن کد کمتری نسبت به دستور ضروری دارد. این به معنای خرابی کمتر، کد کمتر برای آزمایش و چرخه توسعه سازنده تر است. از آنجایی که سیستم دائما در حال تکامل و رشد است، این مهم است.

    اجزای اصلی کاکائو راکتیو

    برنامه نویسی تابعی به مفاهیمی می پردازد که به عنوان آینده (نمایش فقط خواندنی یک متغیر) و وعده (نمایش فقط خواندنی یک آینده متغیر) شناخته می شوند. چه چیزی در مورد آنها خوب است؟ در برنامه نویسی ضروری، باید با مقادیری کار کنید که از قبل وجود دارند، که منجر به نیاز به همگام سازی کدهای ناهمزمان و سایر مشکلات می شود. اما مفاهیم آتی و وعده ها به شما امکان می دهد با مقادیری کار کنید که هنوز ایجاد نشده اند (کد ناهمزمان به روش همزمان نوشته شده است).


    علامت

    آینده و وعده به عنوان سیگنال در برنامه نویسی واکنشی نشان داده می شوند. - جزء اصلی ReactiveCocoa. این فرصتی را برای ارائه جریان رویدادهایی که در آینده ارائه خواهد شد فراهم می کند. شما در یک سیگنال مشترک می شوید و به رویدادهایی که در طول زمان اتفاق می افتد دسترسی پیدا می کنید. سیگنال یک رشته تحت فشار است و می تواند فشار دکمه، عملیات شبکه ناهمزمان، تایمر، سایر رویدادهای رابط کاربری یا هر چیز دیگری باشد که در طول زمان تغییر می کند. آنها می توانند نتایج عملیات ناهمزمان را به هم پیوند دهند و منابع رویداد متعدد را به طور موثر ترکیب کنند.

    دنباله

    نوع دیگری از جریان، توالی است. بر خلاف سیگنال، یک دنباله یک جریان کششی است. این یک نوع مجموعه است که هدفی مشابه با NSArray دارد. RACsequence اجازه می دهد تا عملیات خاصی را در زمانی که به آنها نیاز دارید انجام دهید، نه به صورت متوالی، مانند یک مجموعه. NSArray. مقادیر در یک دنباله تنها زمانی ارزیابی می شوند که به طور پیش فرض مشخص شده باشند. استفاده از تنها بخشی از دنباله به طور بالقوه عملکرد را بهبود می بخشد. دنباله RACبه مجموعه‌های کاکائو اجازه می‌دهد تا به روشی عمومی و اعلامی مدیریت شوند. RAC متد -rac_sequence را به اکثر کلاس‌های مجموعه کاکائو اضافه می‌کند تا بتوان از آنها به عنوان استفاده کرد RACsequences.

    تیم

    در پاسخ به برخی اقدامات، دستور RACCو در سیگنال مشترک شوید. این در درجه اول برای تعاملات UI صدق می کند. دسته بندی ها UIKitبرای اکثر کنترل ها توسط ReactiveCocoa ارائه شده است UIKit، روش صحیح مدیریت رویدادهای رابط کاربری را به ما ارائه دهید. بیایید تصور کنیم که باید در پاسخ به کلیک یک دکمه کاربر را ثبت کنیم. در این مورد، فرمان ممکن است یک درخواست شبکه را نشان دهد. هنگامی که فرآیند شروع می شود، دکمه حالت خود را به "غیر فعال" تغییر می دهد و بالعکس. چه چیز دیگری؟ ما می توانیم یک سیگنال فعال را در یک دستور ارسال کنیم (Reachability مثال خوبی است). بنابراین، اگر سرور در دسترس نباشد (که "سیگنال روشن" ما است)، دستور در دسترس نخواهد بود و هر فرمان کنترل مرتبط این حالت را منعکس می کند.

    نمونه هایی از عملیات پایه

    در اینجا چند نمودار از نحوه عملکرد عملیات اصلی با RACSignals آورده شده است:

    ادغام

    + (RACSignal *)ادغام:(id ) سیگنال ها؛


    جریان‌های نتیجه، هر دو جریان رویداد را به هم پیوسته‌اند. بنابراین "+merge" زمانی مفید است که شما به منبع خاصی از رویدادها اهمیت نمی دهید، اما می خواهید آنها را در یک مکان مدیریت کنید. در مثال ما، stateLabel.text از 3 سیگنال مختلف استفاده می کند: تکمیل، تکمیل، خطا.

    RACCommand *loginCommand = [ initWithSignalBlock:^RACSignal *(ورودی شناسه) ( // اجازه دهید وارد شوید!)]؛ RACSignal *executionSignal = ؛ RACSignal *completionSignal = filter:^BOOL(RACEvent *event) (return event.eventy RACEventTypeCompleted؛ )] نقشه:^id(مقدار id) (بازگشت @"Done"؛ )]؛ )]؛ RACSignal *errorSignal = ؛ RAC(self.stateLabel, text) = ];

    + (RACSignal *)combineLatest:(id )سیگنال ها کاهش می یابند:(id (^)())reduceBlock;

    در نتیجه، جریان حاوی آخرین مقادیر جریان های در حال انتقال است. اگر یکی از جریان ها مهم نباشد، نتیجه خالی خواهد بود.


    چه زمانی می توانیم از آن استفاده کنیم؟ بیایید مثال قبلی خود را در نظر بگیریم و منطق بیشتری به آن اضافه کنیم. فعال کردن دکمه ورود فقط زمانی مفید است که کاربر ایمیل و رمز عبور صحیح را وارد کرده باشد، درست است؟ ما می توانیم این قانون را به این صورت اعلام کنیم:

    ACSignal *enabledSignal = reduce:^id (NSString *email, NSString *password) ( بازگشت @( && password.length > 3); )];

    *حالا اجازه دهید دستور ورود خود را کمی تغییر دهیم و آن را به دکمه ورود واقعی متصل کنیم.

    RACCommand *loginCommand = [ initWithEnabled:enabledSignalBlock:^RACSignal *(input id) ( // let"s login! )]; ;

    - (RACSignal *)flattenMap:(RACSstream * (^)(id value))block;

    شما برای هر مقدار در جریان اصلی با استفاده از تابع داده شده (f) جریان های جدیدی ایجاد می کنید. جریان نتیجه سیگنال های جدیدی را بر اساس مقادیر تولید شده در جریان های اصلی برمی گرداند. بنابراین می تواند ناهمزمان باشد.


    بیایید تصور کنیم که درخواست مجوز شما به سیستم از دو بخش جداگانه تشکیل شده است: دریافت اطلاعات از فیس بوک (ID و غیره) و ارسال آن به Backend. یکی از الزامات این است که بتوانید ورود را لغو کنید. بنابراین، کد مشتری باید وضعیت فرآیند ورود را کنترل کند تا بتواند آن را لغو کند. این کدهای دیگ بخار زیادی می دهد، به خصوص اگر بتوانید از چندین مکان وارد شوید.

    ReactiveCocoa چگونه به شما کمک می کند؟ این می تواند یک پیاده سازی ورود به سیستم باشد:

    - (RACSignal *)authorizeUsingFacebook ( بازگشت [[ flattenMap:^RACStream *(FBSession *session) (بازگشت ; )] flattenMap:^RACSstream *(NSDictionary *profile) (بازگشت ; )]؛)

    افسانه:

    + - سیگنالی که به دهانه منتهی می شود جلسه FBS. در صورت لزوم، این ممکن است منجر به ورود به فیس بوک.

    - - سیگنالی که داده های نمایه را از طریق یک جلسه بازیابی می کند، که به عنوان ارسال می شود خود.

    مزیت این رویکرد این است که برای کاربر، کل جریان فازی است، که با یک سیگنال منفرد نشان داده می شود که می تواند در هر "مرحله ای" لغو شود، خواه ورود به فیس بوک باشد یا تماس Backend.

    فیلتر کنید

    - (RACSignal *)filter:(BOOL (^)(id value))block;

    در نتیجه، جریان حاوی مقادیر جریان "a" است که مطابق تابع داده شده فیلتر شده است.


    RACsequence *sequence = @[@"Some"، @"example، @"of"، @"sequence].rac_sequence; RACsequence *filteredSequence = ; )]؛

    نقشه

    - (RACSignal *)map:(id (^)(id value))block;

    بر خلاف FlattenMap، Map به صورت همزمان اجرا می شود. مقدار ویژگی "a" از تابع داده شده f (x + 1) عبور می کند و مقدار اولیه نگاشت شده را برمی گرداند.


    فرض کنید می خواهید عنوان یک مدل را روی صفحه وارد کنید و برخی ویژگی ها را به آن اعمال کنید. نقشه زمانی وارد عمل می‌شود که «به‌کارگیری برخی ویژگی‌ها» به عنوان یک تابع مستقل توصیف شود:

    RAC(self.titleLabel، text) = initWithString:modelTitle ویژگی ها: ویژگی ها]; )]؛

    چگونه کار می کند: متحد می شود self.titleLabel.textبا تغییرات مدل. عنوانبا اعمال ویژگی های سفارشی برای آن.

    زیپ

    + (RACSignal *)zip:(id )streams reduce:(id (^)())reduceBlock;

    رویدادهای جریان نتایج زمانی ایجاد می‌شوند که هر یک از جریان‌ها تعداد مساوی رویداد را ایجاد کرده باشند. این شامل مقادیر، یکی از هر یک از 3 جریان ترکیبی است.


    برای چند مثال عملی، zip را می توان به صورت توصیف کرد dispatch_group_notifyبه عنوان مثال، شما 3 سیگنال جداگانه دارید و می خواهید پاسخ های آنها را در یک نقطه ترکیب کنید:

    NSArray *signals = @; برگشت؛

    - (RACS سیگنال *)دریچه گاز:(NSTimeInterval)فاصله؛

    با تنظیم تایمر برای مدت زمان معینی، اولین مقدار جریان "a" تنها زمانی به جریان نتیجه منتقل می شود که تایمر منقضی شود. در صورتی که مقدار جدیدی در بازه زمانی معین تولید شود، اولین مقدار را نگه می دارد و از انتقال آن به جریان نتیجه جلوگیری می کند. در عوض، یک مقدار دوم در جریان نتیجه ظاهر می شود.


    یک مورد شگفت‌انگیز: زمانی که کاربر searchField را تغییر می‌دهد، باید یک پرس و جو را جستجو کنیم. مسئله استاندارد، درست است؟ با این حال، برای ایجاد و ارسال درخواست شبکه در هر بار تغییر متن کارایی چندانی ندارد، زیرا textField می تواند بسیاری از چنین رویدادهایی را در ثانیه ایجاد کند و در نهایت با استفاده ناکارآمد از شبکه مواجه می شوید.
    راه حل در اینجا اضافه کردن یک تاخیر است که پس از آن در واقع درخواست شبکه را انجام می دهیم. این معمولا با اضافه کردن NSTimer به دست می آید. با ReactiveCocoa بسیار ساده تر است!

    [[ throttle:0.3] subscribeNext:^(NSString *text) ( // انجام درخواست شبکه )];

    *یک نکته مهم در اینجا این است که تمام فیلدهای متنی "قبلی" قبل از حذف "آخرین" اصلاح می شوند.

    تاخیر

    - (RACSignal *)تاخیر:(NSTimeInterval)فاصله؛

    مقدار دریافت شده در جریان "a" به تاخیر افتاده و پس از یک بازه زمانی معین به جریان نتیجه منتقل می شود.


    مانند -، تاخیر فقط ارسال رویدادهای "بعدی" و "تکمیل شده" را به تاخیر می اندازد.

    [ subscribeNext:^(NSString *text) ( )];

    آنچه ما در مورد کاکائو راکتیو دوست داریم

    • Cocoa Bindings را به iOS معرفی می کند
    • امکان ایجاد عملیات بر روی داده های آینده. در اینجا برخی از نظریه ها در مورد آینده و وعده ها از Scala است.
    • توانایی نمایش عملیات ناهمزمان به روش همزمان. Reactive Cocoa نرم افزارهای ناهمزمان مانند کد شبکه را ساده می کند.
    • تجزیه راحت. کدهایی که با رویدادهای کاربر و تغییرات وضعیت برنامه سروکار دارند می توانند بسیار پیچیده و گیج کننده شوند. کاکائو واکنشی مدل های عملیات وابسته را به ویژه ساده می کند. هنگامی که ما عملیات را به صورت رشته های به هم پیوسته نشان می دهیم (مثلاً پردازش درخواست شبکه، رویدادهای کاربر و غیره)، می توانیم به ماژولاریت بالا و اتصال آزاد دست پیدا کنیم که در نتیجه استفاده مجدد از کد بیشتر می شود.
    • رفتارها و روابط بین ویژگی ها به عنوان اعلانی تعریف می شوند.
    • مشکلات همگام سازی را حل می کند - اگر چندین سیگنال را ترکیب کنید، یک مکان واحد برای رسیدگی به همه نتایج (اعم از مقدار بعدی، تکمیل یا سیگنال خطا) وجود دارد.

    با چارچوب RAC، می توانید دنباله هایی از مقادیر را به روشی بهتر و سطح بالاتر ایجاد و تبدیل کنید. RAC مدیریت هر چیزی را که در انتظار تکمیل یک عملیات ناهمزمان است آسان تر می کند: پاسخ شبکه، تغییر مقدار وابسته و واکنش بعدی. مقابله با آن در نگاه اول سخت است، اما ReactiveCocoa مسری است!

    من می خواهم در مورد یک رشته برنامه نویسی مدرن به شما بگویم که نیازهای روزافزون برای مقیاس پذیری، تحمل خطا و پاسخ سریع را برآورده می کند و در محیط های چند هسته ای و رایانش ابری ضروری است، و همچنین یک دوره آنلاین باز در مورد آن ارائه می دهد. فقط چند روز دیگر شروع شود

    اگر چیزی در مورد برنامه نویسی واکنشی نشنیده اید، خوب هستید که بروید. این یک رشته به سرعت در حال توسعه است که همزمانی را با رویداد محور و ناهمزمان ترکیب می کند. واکنش پذیری در هر وب سرویس و سیستم توزیع شده ذاتی است و هسته اصلی بسیاری از سیستم های با کارایی بالا با درجه موازی بالا است. به طور خلاصه، نویسندگان دوره پیشنهاد می کنند برنامه نویسی واکنشی را به عنوان یک گسترش طبیعی برنامه نویسی تابعی (با توابع مرتبه بالاتر) به سیستم های موازی با حالت توزیع شده، هماهنگ و هماهنگ شده توسط جریان های داده ناهمزمان که توسط افراد فعال رد و بدل می شود، در نظر بگیرند، یا بازیگران.

    به عبارت قابل فهم تر، این در مانیفست واکنشی توضیح داده شده است، در زیر مفاد اصلی آن را بازگو خواهم کرد و ترجمه کامل آن در هابره منتشر شده است. همانطور که ویکی پدیا می گوید، اصطلاح برنامه نویسی واکنشیبرای مدت طولانی وجود داشته است و کاربردهای عملی با درجات مختلف عجیب و غریب دارد، اما اخیراً به لطف تلاش های نویسندگان مانیفست واکنشی، یک گروه ابتکاری از Typesafe Inc، انگیزه جدیدی برای توسعه و توزیع دریافت کرده است. Typesafe در جامعه برنامه نویسی تابعی به عنوان شرکتی شناخته می شود که توسط نویسندگان زبان عالی اسکالا و پلت فرم موازی انقلابی Akka تأسیس شده است. اکنون آنها شرکت خود را به عنوان خالق اولین پلت فرم جت جهان که برای توسعه نسل جدید طراحی شده است، معرفی می کنند. پلتفرم آنها توسعه سریع رابط های کاربری پیچیده را امکان پذیر می کند و سطح جدیدی از انتزاع را در محاسبات موازی و چند رشته ای ارائه می دهد و خطرات ذاتی آنها را با مقیاس بندی قابل پیش بینی تضمین شده کاهش می دهد. ایده های Reactive Manifesto را عملی می کند و به توسعه دهنده اجازه می دهد تا برنامه هایی را که نیازهای امروزی را برآورده می کند، درک کرده و ایجاد کند.

    شما می توانید با شرکت در دوره آموزشی باز آنلاین Massive Principles Reactive با این پلتفرم و برنامه نویسی واکنشی آشنا شوید. این دوره ادامه دوره "اصول برنامه نویسی عملکردی در اسکالا" مارتین اودرسکی است که بیش از 100000 شرکت کننده داشته است و یکی از بالاترین میزان موفقیت را برای یک دوره آزاد گسترده آنلاین توسط شرکت کنندگان خود در جهان به نمایش گذاشته است. همراه با خالق زبان اسکالا، دوره جدید توسط اریک مایر، که چارچوب Rx را برای برنامه نویسی واکنشی تحت دات نت توسعه داده و رولاند کوهن، که در حال حاضر تیم توسعه Akka در Typesafe را رهبری می کند، تدریس می شود. این دوره عناصر کلیدی برنامه نویسی واکنشی را پوشش می دهد و نشان می دهد که چگونه آنها را برای طراحی سیستم های رویداد محور که مقیاس پذیر و مقاوم به خطا هستند به کار می گیرند. مطالب آموزشی با برنامه های کوتاه به تصویر کشیده شده و با مجموعه ای از وظایف همراه است که هر کدام یک پروژه نرم افزاری هستند. در صورت انجام موفقیت آمیز کلیه وظایف، شرکت کنندگان گواهینامه دریافت می کنند (البته شرکت و گواهینامه رایگان است). این دوره 7 هفته به طول می انجامد و از دوشنبه 13 آبان آغاز می شود. یک طرح کلی و همچنین یک ویدیوی مقدماتی در صفحه دوره موجود است: https://www.coursera.org/course/reactive.

    برای کسانی که علاقه مند هستند یا شک دارند، خلاصه ای مختصر از مفاهیم اساسی مانیفست واکنشی ارائه می کنم. نویسندگان آن به تغییرات قابل توجهی در الزامات برنامه های کاربردی در سال های اخیر اشاره می کنند. امروزه برنامه ها در هر محیطی از دستگاه های تلفن همراه گرفته تا خوشه های ابری با هزاران پردازنده چند هسته ای مستقر می شوند. این محیط ها تقاضاهای جدیدی را برای نرم افزار و فناوری ایجاد می کنند. معماری‌های نسل قبلی بر سرورها و کانتینرهای مدیریت‌شده، مقیاس‌بندی از طریق سخت‌افزار گران‌قیمت اضافی، راه‌حل‌های اختصاصی و محاسبات موازی از طریق چند رشته‌ای متمرکز بودند. در حال حاضر یک معماری جدید در حال توسعه است که دارای چهار ویژگی کلیدی است که به طور فزاینده ای در محیط های صنعتی مصرف کننده و شرکت ها رایج است. سیستم‌هایی با این معماری عبارتند از: رویداد محور، مقیاس‌پذیر، مقاوم در برابر خطا (Resilient) و پاسخ سریع دارند، یعنی. پاسخگو (پاسخگو). این یک تجربه کاربری بی‌نقص و بی‌درنگ را فراهم می‌کند که توسط پشته برنامه‌های خود ترمیم‌شونده و مقیاس‌پذیر پشتیبانی می‌شود که آماده استقرار در محیط‌های چند هسته‌ای و ابری است. هر یک از چهار ویژگی یک معماری واکنشی برای کل پشته فناوری اعمال می شود که آنها را از پیوندها در معماری های لایه ای متمایز می کند. بیایید آنها را با کمی جزئیات بیشتر در نظر بگیریم.


    رویداد محوربرنامه‌ها ارتباطات اجزای ناهمزمان را فرض می‌کنند و طراحی جفت‌شده آزاد خود را پیاده‌سازی می‌کنند: فرستنده و گیرنده پیام نیازی به دانستن یکدیگر یا روش انتقال پیام ندارند، که به آنها اجازه می‌دهد بر محتوای ارتباطات تمرکز کنند. علاوه بر این واقعیت که اجزای سست کوپل شده به طور قابل توجهی قابلیت نگهداری، توسعه پذیری و تکامل سیستم را بهبود می بخشد، ماهیت ناهمزمان و غیر مسدود کننده تعامل آنها نیز می تواند بخش قابل توجهی از منابع را آزاد کند، زمان پاسخ را کاهش دهد و ارائه دهد. Oتوان عملیاتی بالاتر از برنامه های سنتی به لطف ماهیت رویداد محور است که بقیه ویژگی های معماری واکنشی امکان پذیر است.

    مقیاس پذیریدر زمینه برنامه نویسی واکنشی، این واکنش سیستم به تغییر بار است، یعنی. خاصیت ارتجاعی که با توانایی اضافه کردن یا آزاد کردن گره های محاسباتی در صورت نیاز به دست می آید. با اتصال کم، پیام‌رسانی ناهمزمان و شفافیت مکان، روش استقرار و توپولوژی برنامه به یک تصمیم زمان استقرار تبدیل می‌شود و تابع پیکربندی پاسخگوی بار و الگوریتم‌های تطبیقی ​​است. بنابراین، شبکه کامپیوتری بخشی از برنامه کاربردی می شود که در ابتدا ماهیت توزیع شده صریح دارد.

    تحمل خطامعماری واکنش‌گرا نیز در حال تبدیل شدن به بخشی از طراحی است، و این باعث می‌شود که آن را به طور قابل‌توجهی با رویکردهای سنتی برای اطمینان از دسترسی مداوم سیستم از طریق افزونگی سرور و خرابی متفاوت کند. انعطاف‌پذیری چنین سیستمی با توانایی آن در پاسخگویی صحیح به خرابی‌های تک تک اجزا، جداسازی این خرابی‌ها با ذخیره زمینه آن‌ها در قالب پیام‌هایی که باعث ایجاد آن‌ها شده‌اند، و انتقال این پیام‌ها به مؤلفه دیگری که می‌تواند در مورد چگونگی تصمیم‌گیری تصمیم‌گیری کند، به دست می‌آید. خطا را مدیریت کند این رویکرد به شما این امکان را می دهد که منطق تجاری برنامه را تمیز نگه دارید و منطق مدیریت خرابی را از آن جدا کنید، که در یک فرم اعلامی صریح برای ثبت، جداسازی و رسیدگی به خرابی ها توسط خود سیستم فرموله شده است. برای ساخت چنین سیستم های خود ترمیمی، اجزا به صورت سلسله مراتبی مرتب می شوند و مشکل به سطحی می رسد که می تواند آن را حل کند.

    و در نهایت پاسخگویی- توانایی سیستم برای پاسخگویی به ورودی کاربر بدون توجه به بار و خرابی است، اینگونه برنامه ها کاربر را در تعامل قرار می دهند، احساس ارتباط نزدیک با سیستم و تجهیزات کافی برای انجام وظایف جاری ایجاد می کنند. پاسخگویی نه تنها در سیستم های بلادرنگ مرتبط است، بلکه برای طیف وسیعی از برنامه ها نیز ضروری است. علاوه بر این، سیستمی که قادر به پاسخگویی سریع حتی در زمان خرابی نیست، نمی تواند قابل تحمل خطا در نظر گرفته شود. پاسخگویی با استفاده از مدل‌های قابل مشاهده، جریان‌های رویداد و مشتریان حالت دار به دست می‌آید. مدل‌های قابل مشاهده زمانی که حالتشان تغییر می‌کند رویدادها را منتشر می‌کنند و تعامل بلادرنگ بین کاربران و سیستم‌ها را فراهم می‌کنند، در حالی که جریان‌های رویداد انتزاعی را ارائه می‌کنند که این تعامل بر اساس آن از طریق تبدیل‌ها و ارتباطات غیرهمزمان غیرمسدود ساخته شده است.

    بنابراین، برنامه های کاربردی واکنشی نشان دهنده یک رویکرد متعادل برای حل طیف گسترده ای از مشکلات توسعه نرم افزار مدرن است. آنها که بر اساس رویداد محور ساخته شده‌اند، ابزارهای مورد نیاز برای تضمین مقیاس‌پذیری و تحمل خطا را فراهم می‌کنند و از یک تجربه کاربر پاسخگو با ویژگی‌های کامل پشتیبانی می‌کنند. نویسندگان انتظار دارند تعداد فزاینده ای از سیستم ها به اصول مانیفست واکنشی پایبند باشند.

    در ضمن طرح دوره رو بدون ترجمه میدم. فقط در صورتی که تا اینجا خوانده باشید و همچنان کنجکاو باشید.

    هفته 1: بررسی اصول برنامه ریزی تابعی: مدل جایگزینی، عبارات برای و نحوه ارتباط آنها با مونادها. یک پیاده سازی جدید از for-expressions را معرفی می کند: مولدهای مقادیر تصادفی. نشان می دهد که چگونه می توان از آن در آزمایش تصادفی استفاده کرد و یک نمای کلی از ScalaCheck، ابزاری که این ایده را اجرا می کند، ارائه می دهد.

    هفته 2: برنامه نویسی تابعی و حالت قابل تغییر. چه چیزی یک شی را تغییر پذیر می کند؟ چگونه این بر مدل جایگزینی تأثیر می گذارد. مثال گسترده: شبیه سازی مدار دیجیتال

    هفته 3: معاملات آتی معاملات آتی را به‌عنوان موناد دیگری معرفی می‌کند، با عبارت‌های for-به‌عنوان نحو مشخص. نشان می‌دهد که چگونه می‌توان قراردادهای آتی را برای جلوگیری از مسدود شدن موضوع تنظیم کرد. در مورد رسیدگی به خطای بین رشته ای بحث می کند.

    هفته 4: پردازش جریان واکنشی. تعمیم معاملات آتی به محاسبات واکنشی بر روی جریان ها. اپراتورهای جریان

    هفته 5:بازیگران. مدل بازیگر را معرفی می‌کند، بازیگران را به عنوان واحدهای محصور شده سازگاری، ارسال پیام ناهمزمان، معناشناسی مختلف تحویل پیام (حداکثر یک بار، حداقل یک بار، دقیقاً یک بار) و سازگاری نهایی را مورد بحث قرار می‌دهد.

    هفته 6: نظارت. تصحیح شکست، مدیریت سلسله مراتبی شکست، الگوی هسته خطا، نظارت بر چرخه حیات را معرفی می کند، وضعیت گذرا و پایدار را مورد بحث قرار می دهد.

    هفته 7: الگوهای مکالمه مدیریت حالت مکالمه بین بازیگران و الگوهای کنترل جریان، مسیریابی پیام‌ها به مجموعه‌های بازیگران برای انعطاف‌پذیری یا متعادل‌سازی بار، تأیید دریافت برای دستیابی به تحویل قابل اعتماد را مورد بحث قرار می‌دهد.

    برو

    برنامه نویسی واکنشی در ابتدا مانند نام یک پارادایم نوپا به نظر می رسد، اما در واقع به یک روش برنامه نویسی اشاره دارد که از رویکرد رویداد محور برای کار با جریان های داده ناهمزمان استفاده می کند. بر اساس داده‌های دائمی جاری، سیستم‌های واکنش‌گرا با اجرای مجموعه‌ای از رویدادها به آنها واکنش نشان می‌دهند.
    برنامه نویسی واکنشی از الگوی طراحی Observer پیروی می کند که می توان آن را به صورت زیر تعریف کرد: اگر تغییر حالت در یک شی اتفاق بیفتد، تمام اشیاء دیگر مطابق با آن مطلع و به روز می شوند. بنابراین به جای نظرسنجی از رویدادها برای تغییرات، رویدادها به صورت ناهمزمان تحت فشار قرار می گیرند تا ناظران بتوانند آنها را پردازش کنند. در این مثال، ناظرها توابعی هستند که هنگام ارسال یک رویداد اجرا می شوند. و جریان داده ذکر شده قابل مشاهده واقعی است.

    تقریباً همه زبان ها و فریم ورک ها از این رویکرد در اکوسیستم خود استفاده می کنند و آخرین نسخه های جاوا نیز از این قاعده مستثنی نیستند. در این مقاله توضیح خواهم داد که چگونه می توان برنامه نویسی واکنشی را با استفاده از آخرین نسخه JAX-RS در عملکرد Java EE 8 و Java 8 اعمال کرد.

    مانیفست واکنشی

    Reactive Manifesto چهار جنبه اساسی را فهرست می‌کند که یک برنامه کاربردی باید انعطاف‌پذیرتر باشد، جفت‌شده‌تر، و مقیاس‌پذیری آن آسان‌تر باشد و بنابراین بتواند واکنش‌پذیر باشد. می گوید که برنامه باید پاسخگو، انعطاف پذیر (و در نتیجه مقیاس پذیر)، انعطاف پذیر و پیام محور باشد.

    هدف اساسی یک برنامه کاربردی واقعا پاسخگو است. فرض کنید برنامه‌ای دارید که یک رشته بزرگ درخواست‌های کاربر را پردازش می‌کند، و وقتی کار انجام شد، آن رشته پاسخ‌ها را به درخواست‌کنندگان اصلی ارسال می‌کند. هنگامی که یک برنامه بیشتر از آنچه می تواند درخواست کند، درخواست دریافت می کند، این جریان به یک گلوگاه تبدیل می شود و برنامه پاسخگویی قبلی خود را از دست می دهد. برای اینکه برنامه پاسخگو باقی بماند، باید مقیاس پذیر و انعطاف پذیر باشد. یک برنامه انعطاف پذیر برنامه ای است که قابلیت بازیابی خودکار دارد. در تجربه اکثر توسعه دهندگان، فقط یک معماری پیام محور به یک برنامه اجازه می دهد تا مقیاس پذیر، قوی و پاسخگو باشد.

    برنامه نویسی واکنشی در جاوا 8 و جاوا EE 8 معرفی شد. زبان جاوا مفاهیمی مانند CompletionStage و اجرای CompletableFuture آن را معرفی کرد و جاوا شروع به استفاده از این ویژگی ها در مشخصاتی مانند Reactive Client API در JAX-RS کرد.

    JAX-RS 2.1 Reactive Client API

    بیایید به نحوه استفاده از برنامه نویسی واکنشی در برنامه های Java EE 8 نگاهی بیندازیم. برای درک این فرآیند، به دانش اولیه Java EE API نیاز دارید.

    JAX-RS 2.1 راه جدیدی را برای ایجاد یک مشتری REST واکنشی معرفی کرد. پیاده سازی پیش فرض فراخوان ارائه شده توسط JAX-RS همزمان است، به این معنی که کلاینت ایجاد شده یک تماس مسدود کننده را به نقطه پایانی سرور ارسال می کند. نمونه ای از پیاده سازی در فهرست 1 ارائه شده است.

    پاسخ = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .get();
    با شروع نسخه 2.0، JAX-RS از ایجاد یک فراخوان ناهمزمان در API مشتری با یک فراخوانی ساده به متد async() پشتیبانی می کند، همانطور که در لیست 2 نشان داده شده است.

    آینده answer = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .async() .get();
    استفاده از یک فراخوان ناهمزمان در کلاینت نمونه Future از نوع javax.ws.rs.core.Response را برمی گرداند. این می تواند منجر به نظرسنجی برای یک پاسخ، فراخوانی () future.get، یا ثبت یک تماس برگشتی شود که در صورت وجود پاسخ HTTP در دسترس، فراخوانی می شود. هر دو پیاده‌سازی برای برنامه‌نویسی ناهمزمان مناسب هستند، اما اگر بخواهید تماس‌های برگشتی را گروه‌بندی کنید یا موارد شرطی را به حداقل‌های اجرای ناهمزمان اضافه کنید، اوضاع پیچیده‌تر می‌شود.

    JAX-RS 2.1 یک راه واکنشی برای غلبه بر این مشکلات با API جدید JAX-RS Reactive Client برای مونتاژ مشتری ارائه می دهد. این به سادگی فراخوانی متد rx() در حین ساخت کلاینت است. در فهرست 3، متد rx() یک فراخوان واکنشی را که در طول اجرای کلاینت وجود دارد، برمی‌گرداند، و کلاینت پاسخی با نوع CompletionStage.rx() برمی‌گرداند که امکان انتقال از یک فراخوان همزمان به یک فراخوان ناهمزمان با یک فراخوان ساده را فراهم می‌کند. زنگ زدن.

    مرحله تکمیل answer = ClientBuilder.newClient() .target("http://localhost:8080/service-url") .request() .rx() .get();
    مرحله تکمیل<Т>یک رابط جدید است که در جاوا 8 معرفی شده است. این یک محاسبه را نشان می دهد که همانطور که از نام آن پیداست می تواند یک مرحله در یک محاسبه بزرگتر باشد. این تنها واکنش پذیری جاوا 8 است که آن را به JAX-RS تبدیل کرده است.
    پس از دریافت نمونه پاسخ، می‌توانم AcceptAsync() را فراخوانی کنم، جایی که می‌توانم قطعه کدی را ارائه کنم که همانطور که در لیست 4 نشان داده شده است، زمانی که پاسخ در دسترس قرار گرفت، به صورت ناهمزمان اجرا می‌شود.

    Response.thenAcceptAsync(res -> ( دما t = res.readEntity(Temperature.class); //انجام کار با t ));
    افزودن واکنش پذیری به نقطه پایانی REST

    رویکرد واکنشی به سمت مشتری در JAX-RS محدود نمی شود. می توان از آن در سمت سرور نیز استفاده کرد. به عنوان مثال، ابتدا یک اسکریپت ساده ایجاد می کنم که در آن می توانم لیستی از مکان ها را برای یک مقصد درخواست کنم. برای هر موقعیت، من یک تماس جداگانه با داده های مکان با نقطه دیگری برقرار می کنم تا مقادیر دما را دریافت کنم. تعامل مقاصد مطابق شکل 1 خواهد بود.

    شکل 1. تعامل بین مقاصد

    ابتدا فقط مدل دامنه و سپس خدمات هر مدل را تعریف می کنم. لیست 5 نحوه تعریف کلاس Forecast را نشان می دهد که کلاس های Location و Temperature را در بر می گیرد.

    کلاس عمومی دما ( دمای دوگانه خصوصی؛ مقیاس رشته خصوصی؛ // گیرنده‌ها و تنظیم‌کننده‌ها) کلاس عمومی مکان (نام رشته؛ مکان عمومی() () مکان عمومی (نام رشته) (این. نام = نام؛ ) // دریافت‌کننده‌ها و تنظیم‌کننده‌ها ) کلاس عمومی پیش‌بینی (موقعیت موقعیت مکانی خصوصی؛ دمای دما خصوصی؛ پیش‌بینی عمومی (مکان مکان) (این. مکان = مکان؛)
    برای بسته بندی لیست پیش بینی ها، کلاس ServiceResponse در لیست 6 پیاده سازی شده است.

    کلاس عمومی ServiceResponse ( خصوصی طولانی processingTime ؛ خصوصی لیست پیش بینی ها = ArrayList جدید<>()؛ public void setProcessingTime(long processingTime) ( this.processingTime = processingTime; ) public ServiceResponse forecasts(List پیش بینی ها) ( this.forecasts = forecasts؛ این را برگردانید؛ ) // getters )
    LocationResource که در فهرست 7 نشان داده شده است، سه الگوی مکان‌های بازگردانده شده با مسیر /location را تعریف می‌کند.

    @Path("/location") class public LocationResource ( @GET @Produces(MediaType.APPLICATION_JSON) public Response getLocations() ( لیست مکان ها = ArrayList جدید<>()؛ locations.add (مکان جدید ("لندن")); locations.add(new Location("Istanbul")); locations.add (مکان جدید ("پراگ")); return Response.ok(New GenericEntity >(مکان ها)()).build(); ))
    TemperatureResource، که در فهرست 8 نشان داده شده است، یک مقدار دمای تولید شده به طور تصادفی بین 30 و 50 را برای مکان داده شده برمی گرداند. تاخیر 500 میلی‌ثانیه برای شبیه‌سازی خواندن سنسور به اجرا اضافه شده است.

    @Path("/temperature") class public TemperatureResource ( @GET @Path("/(city)") @Produces(MediaType.APPLICATION_JSON) public Response getAverageTemperature(@PathParam("city") String cityName) (دمای دما = جدید Temperature(); temperature.setTemperature((double) (new Random().nextInt(20) + 30)); temperature.setScale("Celsius")؛ try ( Thread.sleep(500); ) catch (InterruptedException نادیده گرفته شد) ( ignored.printStackTrace(); ) return Response.ok(temperature).build();) )
    ابتدا، پیاده‌سازی یک منبع پیش‌بینی همزمان (به فهرست 9 مراجعه کنید) را نشان می‌دهم که همه مکان‌ها را برمی‌گرداند. سپس برای هر موقعیت، سرویس دما را فراخوانی می کند تا مقادیر بر حسب درجه سانتیگراد بدست آید.

    @Path("/forecast") کلاس عمومی ForecastResource ( @Uri("location") private WebTarget locationTarget; @Uri("دما/(شهر)") خصوصی WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) پاسخ عمومی getLocationsWithT () ( startTime طولانی = System.currentTimeMillis()؛ پاسخ ServiceResponse = ServiceResponse () جدید؛ لیست locations = locationTarget .request() .get(new GenericType >()())؛ forEach(location -> ( Temperature temperature = temperatureTarget .resolveTemplate("city", location.getName()) . request() .get(Temperature.class); answer.getForecasts().add(new Forecast(location) .setTemperature (درجه حرارت))؛ ))؛ long endTime = System.currentTimeMillis(); answer.setProcessingTime(endTime - startTime); return Response.ok(response).build(); ))
    هنگامی که مقصد پیش‌بینی به‌عنوان /forecast درخواست می‌شود، خروجی مشابه فهرست 10 دریافت خواهید کرد. توجه داشته باشید که پردازش درخواست 1.533 میلی‌ثانیه طول کشید، که منطقی است زیرا درخواست دما از سه مکان مختلف به طور همزمان تا 1.5 میلی‌ثانیه اضافه می‌شود.

    ( "پیش بینی": [ ( "مکان": ( "نام": "لندن" ), "دما": ( "مقیاس": "سلسیوس"، "دما": 33 ) ), ( "مکان": ( "نام" ": "استانبول" ), "دما": ( "مقیاس": "سانتیگراد"، "دما": 38) ), ( "محل": ( "نام: "پراگ" ), "دما": ( "مقیاس" ": "سانتیگراد"، "دما": 46 ) ) ]، "زمان پردازش": 1533 )
    تا اینجا همه چیز طبق برنامه پیش می رود. زمان معرفی برنامه‌نویسی واکنش‌گرا در سمت سرور فرا رسیده است، جایی که تماس‌های هر مکان را می‌توان به صورت موازی پس از دریافت همه مکان‌ها انجام داد. این به وضوح می تواند جریان همزمان نشان داده شده را بهبود بخشد. این در لیست 11 انجام می شود که تعریف نسخه واکنشی سرویس پیش بینی را نشان می دهد.

    @Path("/reactiveForecast") کلاس عمومی ForecastReactiveResource (@Uri("location") private WebTarget locationTarget; @Uri("temperature/(city)") private WebTarget temperatureTarget; @GET @Produces(MediaType.APPLICATION_JSON) public void getLoc (@Suspended final AsyncResponse async) ( startTime طولانی = System.currentTimeMillis(); // یک مرحله برای بازیابی مکان های CompletionStage ایجاد کنید > locationCS = locationTarget.request() .rx() .get(new GenericType >() ())؛ // با ایجاد یک مرحله جداگانه در مرحله مکان ها، // که در بالا توضیح داده شد، لیست پیش بینی ها // را مانند یک CompletionStage نهایی CompletionStage جمع آوری کنید. > forecastCS = locationCS.thenCompose(locations -> ( // ایجاد یک مرحله برای دریافت پیش بینی ها // به عنوان یک لیست CompletionStage > forecastList = // مکان‌ها را پخش کنید و هر کدام را پردازش کنید // locations.stream().map(location -> ( // یک مرحله ایجاد کنید تا // مقادیر دمای تنها یک شهر // را با نام آن به دست آورید CompletionStage نهایی tempCS = temperatureTarget .resolveTemplate("city", location.getName()) .request() .rx() .get(Temperature.class); // سپس یک CompletableFuture ایجاد کنید که // حاوی یک نمونه پیش بینی با // یک مکان و یک مقدار دما باشد CompletableFuture.completedFuture(new Forecast(location)) .thenCombine(tempCS, Forecast::setTemperature); )).collect(Collectors.toList()); // نمونه نهایی CompletableFuture را برگردانید که در آن // همه اشیاء تکمیل‌پذیر آینده ارائه شده هستند // بازگشت کامل CompletableFuture.allOf(forecastList.toArray(new CompletableFuture)) .thenApply(v -> forecastList.stream() .map(CompletionCompletable:Future:u ) .map(CompletableFuture::join) .collect(Collectors.toList())); ))؛ // یک نمونه ServiceResponse ایجاد کنید که // شامل لیست کامل پیش بینی ها // به همراه زمان پردازش باشد. // آینده آن را ایجاد کنید و آن را با // forecastCS ترکیب کنید تا پیش بینی ها را دریافت کنید // و در پاسخ سرویس وارد کنید CompletableFuture.completedFuture(new ServiceResponse()) .thenCombine(forecastCS, ServiceResponse::forecasts) .whenCompleteAsync((response, throwable) - > (answer.setProcessingTime(System.currentTimeMillis() - startTime); async.resume(response); )); ))
    اجرای واکنشی ممکن است در نگاه اول پیچیده به نظر برسد، اما پس از یک نگاه دقیق متوجه خواهید شد که بسیار ساده است. در پیاده‌سازی ForecastReactiveResource، ابتدا با استفاده از JAX-RS Reactive Client API با سرویس‌های مکان تماس می‌گیرم. همانطور که در بالا ذکر کردم، این یک افزونه برای Java EE 8 است و به ایجاد یک فراخوانی واکنشی به سادگی با متد rx() کمک می کند.

    اکنون من یک مرحله جدید بر اساس مکان ایجاد می کنم تا لیست پیش بینی ها را جمع آوری کنم. آنها به عنوان لیستی از پیش بینی ها در یک مرحله تکمیل بزرگ به نام forecastCS ذخیره می شوند. در نهایت، من فقط با استفاده از forecastCS یک پاسخ تماس سرویس ایجاد خواهم کرد.

    حال بیایید پیش بینی ها را به عنوان لیستی از مراحل تکمیل تعریف شده در متغیر forecastList جمع آوری کنیم. برای ایجاد یک مرحله تکمیل برای هر پیش‌بینی، داده‌های مکان را پاس می‌کنم و سپس یک متغیر tempCS ایجاد می‌کنم، دوباره با استفاده از JAX-RS Reactive Client API، که سرویس دما را با نام شهر فراخوانی می‌کند. در اینجا، من از متد ()solutTemplate برای ساخت کلاینت استفاده می کنم و این به من اجازه می دهد تا نام شهر را به عنوان پارامتر به سازنده ارسال کنم.

    به عنوان آخرین مرحله استریم، با CompletableFuture.completedFuture() تماس می‌گیرم و نمونه Forecast جدید را به عنوان پارامتر ارسال می‌کنم. من این آینده را با مرحله tempCS ترکیب می کنم تا مقدار دما را برای مکان های تکرار شده داشته باشم.

    متد CompletableFuture.allOf() در لیست 11 لیستی از مراحل تکمیل را به forecastCS تبدیل می کند. اجرای این مرحله زمانی که تمام قراردادهای آتی قابل تکمیل عرضه شده تکمیل شده باشند، آینده بزرگ قابل تکمیل را برمی گرداند.

    پاسخ سرویس نمونه ای از کلاس ServiceResponse است، بنابراین من یک آینده تکمیل شده ایجاد می کنم و سپس مرحله تکمیل forecastCS را با لیست پیش بینی ها الحاق می کنم و زمان پاسخ سرویس را محاسبه می کنم.

    البته برنامه نویسی واکنشی تنها سمت سرور را مجبور به اجرای ناهمزمان می کند. سمت کلاینت تا زمانی که سرور پاسخی را به درخواست کننده ارسال نکند مسدود خواهد شد. برای غلبه بر این مشکل، رویدادهای ارسال شده از سرور (SSEs) می توانند برای ارسال پاسخ جزئی به محض در دسترس قرار گرفتن استفاده شوند، به طوری که مقادیر دما برای هر مکان یک به یک برای مشتری ارسال می شود. خروجی ForecastReactiveResource مشابه لیست 12 خواهد بود. همانطور که در خروجی نشان داده شده است، زمان پردازش 515 میلی ثانیه است که زمان اجرای ایده آل برای دریافت مقادیر دما از یک مکان واحد است.

    ( "پیش بینی": [ ( "مکان": ( "نام": "لندن" ), "دما": ( "مقیاس": "سلسیوس"، "دما": 49 ) ), ( "مکان": ( "نام" ": "استانبول" ), "دما": ( "مقیاس": "سلسیوس"، "دما": 32 ) ), ( "مکان": ( "نام: "پراگ" ), "دما": ( "مقیاس" ": "سانتیگراد"، "دما": 45 ) ) ]، "زمان پردازش": 515 )
    نتیجه

    در مثال‌های این مقاله، ابتدا روشی همزمان برای دریافت پیش‌بینی‌ها با استفاده از خدمات مکان و دما را نشان دادم. سپس، برای انجام پردازش ناهمزمان بین تماس‌های سرویس، به رویکرد واکنشی تغییر مکان دادم. وقتی از JAX-RS Reactive Client API در جاوا EE 8 به همراه کلاس های CompletionStage و CompletableFuture موجود در جاوا 8 استفاده می کنید، قدرت پردازش ناهمزمان از طریق برنامه نویسی واکنشی آزاد می شود.

    برنامه نویسی واکنشی فراتر از اجرای یک مدل ناهمزمان از یک مدل همزمان است. همچنین مفاهیمی مانند مرحله تودرتو را ساده می کند. هرچه بیشتر از آن استفاده شود، مدیریت سناریوهای پیچیده در برنامه نویسی موازی آسان تر خواهد بود.

    با تشکر از توجه شما. مثل همیشه پذیرای نظرات و سوالات شما هستیم.

    می توانید کمک کنید و مبلغی را برای توسعه سایت انتقال دهید

    تا به امروز، تعدادی روش برای برنامه نویسی سیستم های زمان واقعی پیچیده وجود دارد. یکی از این روش ها FRP (FRP) نام دارد. یک الگوی طراحی به نام را جذب کرده است مشاهده کننده (مشاهده کننده) از یک سو، و از سوی دیگر، همانطور که ممکن است حدس بزنید، اصول برنامه نویسی تابعی. در این مقاله برنامه نویسی واکنشی تابعی را با استفاده از مثال پیاده سازی آن در کتابخانه بررسی می کنیم سدیمبرای زبان هاسکل.

    بخش تئوری

    به طور سنتی برنامه ها به سه دسته تقسیم می شوند:

    • دسته ای
    • در ارتباط بودن
    • جت

    تفاوت این سه دسته از برنامه ها در نوع تعامل آنها با دنیای خارج است.

    اجرای برنامه های دسته ای به هیچ وجه با دنیای خارج هماهنگ نیست. آنها را می توان در یک نقطه دلخواه در زمان راه اندازی کرد و زمانی که داده های اولیه پردازش شد و نتیجه به دست آمد تکمیل شوند. برخلاف برنامه‌های دسته‌ای، برنامه‌های تعاملی و واکنش‌گرا در طول کار خود از لحظه شروع تا لحظه توقف به طور مداوم با دنیای خارج ارتباط برقرار می‌کنند.

    یک برنامه واکنشی، بر خلاف یک برنامه تعاملی، به شدت با محیط خارجی هماهنگ است: باید به رویدادهای دنیای بیرون با تاخیر قابل قبولی در میزان وقوع این رویدادها پاسخ دهد. در عین حال، برای یک برنامه تعاملی مجاز است که محیط خارجی را منتظر کند. به عنوان مثال، یک ویرایشگر متن یک برنامه تعاملی است: کاربری که فرآیند تصحیح خطاهای متن را آغاز کرده است، برای ادامه ویرایش، باید منتظر تکمیل آن باشد. با این حال، خلبان خودکار یک برنامه واکنشی است، زیرا زمانی که مانعی رخ می دهد، باید فورا مسیر را اصلاح کند تا یک مانور پرواز انجام دهد، زیرا دنیای واقعی را نمی توان مانند یک کاربر ویرایشگر متن مکث کرد.

    این دو دسته از برنامه‌ها کمی بعد ظاهر شدند، زمانی که کامپیوترها برای کنترل ماشین‌ها مورد استفاده قرار گرفتند و رابط‌های کاربری شروع به توسعه کردند. از آن زمان تاکنون روش‌های پیاده‌سازی بسیاری تغییر کرده‌اند که هر کدام برای اصلاح کاستی‌های قبلی طراحی شده‌اند. در ابتدا، اینها برنامه های ساده مبتنی بر رویداد بودند: مجموعه خاصی از رویدادها در سیستمی که لازم بود به آنها پاسخ داده شود اختصاص داده شد و کنترل کننده هایی برای این رویدادها ایجاد شد. کنترل کننده ها نیز به نوبه خود می توانند رویدادهایی را ایجاد کنند که به دنیای خارج می روند. این مدل ساده بود و طیفی از مسائل تعاملی ساده را حل کرد.

    اما با گذشت زمان، برنامه‌های تعاملی و واکنشی پیچیده‌تر شدند و برنامه‌نویسی رویداد محور به جهنم تبدیل شد. نیاز به ابزارهای پیشرفته تری برای سنتز سیستم های رویداد محور وجود داشت. دو نقص عمده در این روش وجود داشت:

    • حالت ضمنی
    • غیر جبرگرایی

    برای رفع این مشکل ابتدا یک قالب ایجاد شد مشاهده کننده (مشاهده کننده) که رویدادها را به مقادیر متغیر با زمان تبدیل کرد. مجموعه این مقادیر نشان دهنده وضعیت صریح برنامه در آن است. بنابراین پردازش رویداد با کار معمول با داده ها برای برنامه های دسته ای جایگزین شد. توسعه‌دهنده می‌تواند مقادیر قابل مشاهده را تغییر دهد و برای تغییر این مقادیر، کنترل‌کننده‌ها را مشترک کند. هنگام تغییر مقادیری که به آنها بستگی دارد، می توان مقادیر وابسته ای را که طبق یک الگوریتم مشخص تغییر می کرد، پیاده سازی کرد.

    اگرچه اتفاقات در این رویکرد از بین رفته است، اما نیاز به آنها همچنان پابرجاست. یک رویداد همیشه به معنای تغییر در ارزش نیست. به عنوان مثال، یک رویداد بلادرنگ به معنای افزایش شمارنده زمان به میزان یک ثانیه است، اما یک رویداد هشدار هر روز در یک زمان خاص به هیچ وجه به معنای هیچ ارزشی نیست. البته، شما همچنین می توانید این رویداد را با معنای خاصی مرتبط کنید، اما این یک وسیله مصنوعی خواهد بود. به عنوان مثال، می توانیم یک مقدار را وارد کنیم: زنگ_زمان == باقیمانده_تقسیم (زمان_جاری، 24*60*60). اما این دقیقاً همان چیزی نخواهد بود که ما به آن علاقه داریم، زیرا این متغیر به یک ثانیه گره خورده است و در واقع مقدار آن دو بار تغییر می کند. برای اطلاع از اینکه زنگ هشدار خاموش شده است، مشترک باید تشخیص دهد که مقدار واقعی شده است و نه برعکس. مقدار دقیقاً برای یک ثانیه درست خواهد بود و اگر دوره تیک را از یک ثانیه به مثلاً 100 میلی ثانیه تغییر دهیم، مقدار واقعی دیگر یک ثانیه نیست، بلکه این 100 میلی ثانیه خواهد بود.

    ظهور روش برنامه نویسی واکنشی تابعی نوعی پاسخ کارکردگرایان به الگو است. مشاهده کننده. در FRP، رویکردهای توسعه‌یافته دوباره مورد بررسی قرار گرفتند: رویدادها (رویدادها) ناپدید نشدند، اما مقادیر مشاهده‌شده نیز ظاهر شدند و نام‌گذاری شدند. ویژگی ها (رفتارها). یک رویداد در این روش، یک مقدار تولید گسسته است، و یک مشخصه، یک مقدار تولید شده به طور مداوم است. هر دوی آنها را می توان به هم مرتبط کرد: ویژگی ها می توانند رویدادها را ایجاد کنند، و رویدادها می توانند به عنوان منبعی برای ویژگی ها عمل کنند.

    مشکل عدم قطعیت دولت بسیار پیچیده تر است. این از این واقعیت ناشی می شود که سیستم های رویداد محور عملاً ناهمزمان هستند. این مستلزم ظهور حالت های میانی سیستم است که ممکن است به دلایلی غیرقابل قبول باشد. برای حل این مشکل، برنامه نویسی به اصطلاح همزمان ظاهر شد.

    بخش عملی

    کتابخانه سدیمبه عنوان یک پروژه اجرایی ظاهر شد FRPبا یک رابط مشترک در زبان های برنامه نویسی مختلف. این شامل تمام عناصر روش شناسی است: اصول اولیه (رویداد، رفتار) و الگوهای استفاده از آنها.

    بدوی ها و تعامل با دنیای خارج

    دو اصل اولیه که باید با آنها کار کنیم عبارتند از:

    • رویداد آ- رویداد با مقدار نوع آ
    • رفتار - اخلاق آ- یک مشخصه (یا مقدار متغیر) یک نوع آ

    ما می توانیم رویدادها و مقادیر جدید را با توابع ایجاد کنیم رویداد جدیدو رفتار جدید:

    newEvent::Reactive(Event a, a -> Reactive()) newBehavior::a -> Reactive(Behavior a, a -> Reactive())

    همانطور که می بینید، هر دوی این توابع فقط در یک موناد قابل فراخوانی هستند. واکنش پذیر، در نتیجه، خود اولیه و همچنین تابعی که برای فعال کردن رویداد یا تغییر مقدار باید فراخوانی شود، برگردانده می شود. تابع ایجاد ویژگی مقدار اولیه را به عنوان اولین آرگومان می گیرد.

    برای اتصال دنیای واقعی با یک برنامه واکنشی، یک تابع وجود دارد همگام سازیو برای ارتباط برنامه با دنیای بیرون، یک تابع وجود دارد گوش بده:

    همگام سازی:: واکنشی a -> IO a listen:: رویداد a -> (a -> IO ()) -> واکنشی (IO ())

    اولین تابع، همانطور که از نام آن پیداست، برخی از کدهای واکنشی را به صورت همزمان اجرا می کند، به شما امکان می دهد وارد متن شوید. واکنش پذیرخارج از زمینه IO، و دومی برای اضافه کردن کنترل کننده های رویداد که در متن رخ می دهد استفاده می شود واکنش پذیر، اجرا در زمینه IO. تابع گوش بدهیک تابع را برمی گرداند گوش ندادنبرای جدا کردن کنترل کننده فراخوانی می شود.

    بنابراین، نوعی مکانیسم تراکنش اجرا می شود. وقتی کاری را در داخل Reactive monad انجام می دهیم، کد در همان تراکنش در زمانی که تابع فراخوانی می شود، اجرا می شود. همگام سازی. حالت فقط خارج از چارچوب یک معامله تعیین می شود.

    این اساس برنامه نویسی تابعی واکنشی است که برای نوشتن برنامه ها کافی است. این می تواند کمی گیج کننده باشد که شما فقط می توانید به رویدادها گوش دهید. این دقیقاً همان طور است که باید باشد، همانطور که بعداً خواهیم دید، روابط نزدیکی بین رویدادها و ویژگی ها وجود دارد.

    عملیات بر روی اصول اولیه اولیه

    برای راحتی، توابع اضافی به روش اضافه شده است که رویدادها و ویژگی ها را تغییر می دهد. بیایید برخی از آنها را در نظر بگیریم:

    رویدادی که هرگز اتفاق نمی‌افتد -- (می‌تواند به‌عنوان خرد استفاده شود) never:: رویداد a -- دو رویداد از یک نوع را در یک رویداد ادغام کنید -- (برای تعریف یک کنترل‌کننده برای کلاس رویداد مفید است) ادغام:: رویداد a -> رویداد a -> رویداد a - مقادیر را از رویدادهای شاید بکشید - (گندم را از کاه جدا کنید) filterJust:: رویداد (شاید a) -> رویداد a - تبدیل یک رویداد به یک مشخصه با یک علامت اولیه مقدار -- (تغییر مقدار زمانی که رویدادها رخ می دهند) نگه دارید:: a -> رویداد a -> واکنشی (رفتار a) - یک مشخصه را به یک رویداد تبدیل می کند - (هنگامی که مقدار تغییر می کند رویدادها ایجاد می کند) به روز رسانی:: رفتار a -> رویداد a -- یک مشخصه را به یک رویداد تبدیل می کند -- (همچنین یک رویداد را برای مقدار اولیه پرتاب می کند) مقدار:: رفتار a -> رویداد a -- هنگامی که یک رویداد رخ می دهد، مقدار مشخصه را می گیرد، -- تابع را اعمال می کند و یک رویداد عکس فوری تولید می کند:: (a -> b -> c) -> رویداد a -> رفتار b - > رویداد c - مقدار فعلی نمونه مشخصه را دریافت می کند:: رفتار a -> واکنشی a - کاهش تکرار رویدادها در یک ادغام:: (a -> a -> a) -> رویداد a -> رویداد a - همه رویدادها را به جز اولین یک بار سرکوب می کند:: رویداد a -> رویداد a - رویداد لیست را به چند رویداد تقسیم می کند :: رویداد [a] -> رویداد a

    نمونه های کروی در خلاء

    بیایید سعی کنیم چیزی بنویسیم:

    وارد کردن FRP.Sodium main = همگام سازی $ do -- ایجاد رویداد (e1، triggerE1)<- newEvent -- создаём характеристику с начальным значением 0 (v1, changeV1) <- newBehavior 0 -- определяем обработчик для события listen e1 $ \_ ->putStrLn $ "e1 triggered" -- یک کنترل کننده برای تغییر مقدار مشخصه listen (مقدار v1) تعریف کنید $ \v -> putStrLn $ "v1 value: " ++ show v -- ایجاد یک رویداد بدون مقدار triggerE1 () - مقدار تغییر مشخصه V1 13 را تغییر دهید

    بسته را نصب کنید سدیمبا استفاده از کابالو مثال را در مفسر اجرا کنید:

    # اگر بخواهیم در یک sandbox جداگانه کار کنیم # آن را ایجاد کنیم > cabal sandbox init # install > cabal install sodium > cabal repl GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? برای کمک بارگیری بسته ghc-prim ... پیوند دادن ... انجام شد. بارگیری بسته عدد صحیح-gmp ... پیوند ... انجام شد. بارگیری پایه بسته ... پیوند ... انجام شد. # بار مثال Prelude> :l Example.hs در حال کامپایل اصلی (Example.hs، تفسیر شده) بسیار خوب، ماژول ها بارگیری شدند: اصلی. # مثال *Main> را اجرا کنید

    حالا بیایید آزمایش کنیم. بیایید خطی را که در آن مقدار خود را تغییر می‌دهیم، نظر بدهیم (changeV1 13) و مثال را دوباره راه‌اندازی کنیم:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *Main> main> e1 main triggered v1 value: 0

    همانطور که می بینید، مقدار اولیه اکنون نمایش داده می شود، دلیل آن این است که تابع ارزشاولین رویداد را با مقدار اولیه مشخصه ایجاد می کند. بیایید تابع را جایگزین کنیم ارزشبر به روز رسانی هاو ببینید چه اتفاقی می افتد:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *اصلی> اصلی e1 فعال شد

    اکنون مقدار اولیه نمایش داده نمی شود، اما اگر خطی که در آن مقدار را تغییر داده ایم را از کامنت برداریم، مقدار تغییر یافته همچنان نمایش داده می شود. بیایید همه چیز را همانطور که بود برگردانیم و یک رویداد ایجاد کنیم e1دو برابر:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *Main> main> main> e1 triggered e1 triggered v1 value: 13

    همانطور که می بینید، این رویداد نیز دو بار شلیک شد. بیایید سعی کنیم از این، چرا در تابع اجتناب کنیم گوش بدهاستدلال را جایگزین کنید e1بر (یک بار e1)، بدین ترتیب یک رویداد جدید ایجاد می شود که یک بار فعال می شود:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *Main> main> e1 main triggered v1 value: 13

    وقتی یک رویداد هیچ استدلالی ندارد، وجود یا عدم وجود رویداد برای ما مهم است، بنابراین عملکرد یک باربرای ترکیب رویدادها انتخاب درستی است. با این حال، هنگامی که یک استدلال وجود دارد، این همیشه مناسب نیست. بیایید مثال را به صورت زیر بازنویسی کنیم:

    <- newEvent (v1, changeV1) <- newBehavior 0 listen e1 $ \v ->putStrLn $ "e1 با: " ++ show v listen (value v1) $ \v -> putStrLn $ "v1 value: " ++ show v triggerE1 "a" triggerE1 "b" triggerE1 "c" changeV1 13

    همانطور که انتظار می رود، همه رویدادها را با مقادیر به همان ترتیبی که تولید شده اند دریافت می کنیم:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *اصلی> e1 اصلی فعال شده با: "a" e1 راه اندازی شده با: "b" e1 راه اندازی شده با: "c" v1 مقدار: 13

    اگر از تابع استفاده کنیم یک باربا e1، سپس فقط اولین رویداد را دریافت می کنیم، بنابراین بیایید سعی کنیم از تابع استفاده کنیم یکی شدن، که برای آن استدلال را جایگزین می کنیم e1 V گوش بدهبحث و جدل (ادغام (\_ a -> a) e1):

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *Main> main e1 با: "c" v1 مقدار: 13 راه اندازی شده است

    و در واقع، ما فقط آخرین رویداد را دریافت کردیم.

    نمونه های بیشتر

    بیایید به مثال های پیچیده تر نگاه کنیم:

    وارد کردن FRP.Sodium main = همگام سازی $do(e1,triggerE1)<- newEvent -- создаём характеристику, изменяемую событием e1 v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 با: " ++ show v listen (مقدار v1) $ \v -> putStrLn $ "v1 مقدار این است: " ++ show v -- انتشار رویدادها triggerE1 1 triggerE1 2 triggerE1 3

    در اینجا خروجی است:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *اصلی> e1 اصلی راه اندازی شده با: 1 e1 راه اندازی شده با: 2 e1 راه اندازی شده با: 3 مقدار v1 است: 3

    مقدار مشخصه فقط یک بار نمایش داده می شود، اگرچه چندین رویداد ایجاد شده است. این ویژگی برنامه نویسی همزمان است: ویژگی ها با تماس هماهنگ می شوند همگام سازی. برای نشان دادن این موضوع، اجازه دهید مثال خود را کمی اصلاح کنیم:

    <- sync $ do (e1, triggerE1) <- newEvent v1 <- hold 0 e1 listen e1 $ \v ->putStrLn $ "e1 با: " ++ show v listen (مقدار v1) $ \v -> putStrLn $ "v1 مقدار است: " ++ show v return triggerE1 sync $ triggerE1 1 sync $ triggerE1 2 sync $ triggerE1 3

    ما فقط ماشه رویداد را به دنیای خارج منتقل کردیم و آن را در مراحل مختلف هماهنگ سازی می نامیم:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *مقدار اصلی > v1 اصلی است: 0 e1 راه اندازی شده با: 1 مقدار v1 است: 1 e1 راه اندازی شده با: 2 مقدار v1 است: 2 e1 راه اندازی شده با: 3 مقدار v1 است: 3

    اکنون هر رویداد یک مقدار جدید را نشان می دهد.

    سایر عملیات بر روی اولیه ها

    گروهی از توابع مفید زیر را در نظر بگیرید:

    رویدادها را با استفاده از mergeWith:: (a -> a -> a) -> Event a -> Event a -> Event a ادغام می‌کند - رویدادها را فیلتر می‌کند که فقط آنهایی را که تابع برای آنها true filterE برمی‌گرداند:: (a -> Bool) ) -> Event a -> Event a -- به "خاموش کردن" رویدادها اجازه می دهد -- زمانی که مشخصه False gate باشد:: Event a -> Behavior Bool -> Event a -- یک مبدل رویداد را سازماندهی می کند -- با وضعیت داخلی collectE: : (a -> s -> (b, s)) -> s -> رویداد a -> واکنشی (رویداد b) - یک مبدل ویژگی را سازماندهی می کند - با حالت داخلی جمع آوری:: (a -> s -> ( b , s)) -> s -> Behavior a -> Reactive (Behavior b) -- یک مشخصه را در نتیجه تجمع رویدادها ایجاد می کند accum:: a -> Event (a -> a) -> Reactive (Behavior a) )

    البته این همه توابع ارائه شده توسط کتابخانه نیست. چیزهای عجیب تری نیز وجود دارد که از حوصله این مقاله خارج است.

    مثال ها

    بیایید این توابع را در عمل امتحان کنیم. بیایید با آخرین مورد شروع کنیم، چیزی مانند یک ماشین حساب سازماندهی کنیم. اجازه دهید مقداری داشته باشیم که بتوانیم توابع حسابی را به آن اعمال کنیم و نتیجه را بدست آوریم:

    وارد کردن FRP.Sodium main = do triggerE1<- sync $ do (e1, triggerE1) <- newEvent -- пусть начальное значение будет равно 1 v1 <- accum (1:: Int) e1 listen (value v1) $ \v ->putStrLn $ "v1 مقدار این است: " ++ show v return triggerE1 -- ​​اضافه کردن 1 sync $ triggerE1 (+ 1) -- ضرب در 2 sync $ triggerE1 (* 2) -- کم کردن 3 sync $ triggerE1 (+ (- 3) ) -- افزودن 5 sync $ triggerE1 (+ 5) -- افزایش به توان 3 sync $ triggerE1 (^ 3)

    بریم بدویم:

    *Main> :l Example.hs در حال کامپایل Main (Example.hs، تفسیر شده) خوب، ماژول ها بارگذاری شده اند: اصلی. *Main> main v1 value is: 1 v1 value is: 2 v1 value is: 4 v1 value is: 1 v1 value is: 6 v1 value is: 216

    ممکن است به نظر برسد که مجموعه ویژگی ها نسبتاً کمیاب است، اما در واقع اینطور نیست. از این گذشته، ما با Haskell سروکار داریم، فانککتورهای کاربردی و مونادها از بین نرفته اند. ما می‌توانیم هر عملیاتی را روی ویژگی‌ها و رویدادهایی که عادت داریم روی مقادیر خالص انجام دهیم، انجام دهیم. در نتیجه ویژگی ها و اتفاقات جدیدی به دست می آید. برای ویژگی‌ها، یک کلاس تابع و یک تابع کاربردی، برای رویدادها، به دلایل واضح، فقط یک تابع اجرا می‌شوند.

    مثلا:

    <$>), (<*>)) وارد کردن FRP.Sodium main = do(setA, setB)<- sync $ do (a, setA) <- newBehavior 0 (b, setB) <- newBehavior 0 -- Новая характеристика a + b let a_add_b = (+) <$>آ<*>b -- ویژگی جدید a * b اجازه دهید a_mul_b = (*)<$>آ<*>b listen (مقدار a) $ \v -> putStrLn $ "a = " ++ show v listen (value b) $ \v -> putStrLn $ "b = " ++ show v listen (مقدار a_add_b) $ \v - > putStrLn $ "a + b = " ++ نشان دادن v گوش دادن (مقدار a_mul_b) $ \v -> putStrLn $ "a * b = " ++ نشان دادن v بازگشت (setA, setB) همگام سازی $ do setA 2 setB 3 همگام سازی $ setA 3 sync $setB 7

    در اینجا چیزی است که در مفسر خروجی خواهد شد:

    λ> اصلی a = 0 b = 0 a + b = 0 a * b = 0 a = 2 b = 3 a + b = 5 a * b = 6 a = 3 a + b = 6 a * b = 9 b = 7 a + b = 10 a * b = 21

    حالا بیایید ببینیم که چنین چیزی با رویدادها چگونه کار می کند:

    ImportControl.Aplicative((<$>)) واردات FRP.Sodium main = do sigA<- sync $ do (a, sigA) <- newEvent let a_mul_2 = (* 2) <$>اجازه دهید a_pow_2 = (^2)<$>a listen a $ \v -> putStrLn $ "a = " ++ show v listen a_mul_2 $ \v -> putStrLn $ "a * 2 = " ++ show v listen a_pow_2 $ \v -> putStrLn $ "a ^ 2 = "++ نشان دادن v بازگشت sigA همگام سازی $do sigA 2 همگام سازی $sigA 3 همگام سازی $sigA 7

    در اینجا چیزی است که خروجی خواهد شد:

    λ> اصلی a = 2 a * 2 = 4 a ^ 2 = 4 a = 3 a * 2 = 6 a ^ 2 = 9 a = 7 a * 2 = 14 a ^ 2 = 49

    مستندات شامل لیستی از نمونه های کلاس است که برای پیاده سازی شده اند رفتار - اخلاقو رویداد، اما هیچ چیز مانع از اجرای نمونه های کلاس های از دست رفته شما نمی شود.

    جنبه منفی واکنش پذیری

    برنامه نویسی واکنشی عملکردی مطمئناً توسعه سیستم های پیچیده بلادرنگ را ساده می کند، اما جنبه های زیادی وجود دارد که باید هنگام استفاده از این رویکرد در نظر گرفت. بنابراین، ما در اینجا مشکلاتی را که اغلب رخ می دهد در نظر خواهیم گرفت.

    عدم همزمانی

    برنامه نویسی همزمان متضمن مکانیسم تراکنش خاصی است که سازگاری حالت های متوالی سیستم و در نتیجه عدم وجود حالت های غیرمنتظره میانی را تضمین می کند. که در سدیمتماس ها مسئول تراکنش ها هستند همگام سازی. اگرچه وضعیت داخل تراکنش تعریف نشده است، اما نمی توان در نظر گرفت که همه چیز در داخل آن به طور همزمان اتفاق می افتد. مقادیر به ترتیب خاصی تغییر می کنند که بر نتیجه تأثیر می گذارد. به عنوان مثال، اشتراک گذاری رویدادها و ویژگی ها می تواند اثرات غیرمنتظره ای داشته باشد. به یک مثال توجه کنید:

    ImportControl.Aplicative((<$>)) وارد کردن FRP.Sodium main = do setVal<- sync $ do (val, setVal) <- newBehavior 0 -- создаём булеву характеристику val >2 اجازه دهید gt2 = (> 2)<$>val -- یک رویداد با مقادیری ایجاد کنید که > 2 هستند اجازه دهید evt = gate (value val) gt2 listen (value val) $ \v -> putStrLn $ "val = " ++ show v listen (value gt2) $ \ v -> putStrLn $ "val > 2 ? " ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return setVal sync $ setVal 1 sync $ setVal 2 sync $ setVal 3 sync $ setVal 4 همگام‌سازی $setVal 0

    می توانید انتظار خروجی اینگونه داشته باشید:

    Val = 0 val > 2 ? False val = 1 val > 2 ? False val = 2 val > 2 ? False val = 3 val > 2 ? True val > 2: 3 val = 4 val > 2 ? True val > 2: 4 val = 0 val > 2 ? نادرست

    با این حال، در واقع خط val > 2: 3غایب خواهد بود و در پایان یک خط وجود خواهد داشت val > 2: 0. این به این دلیل است که رویداد تغییر ارزش رخ می دهد (ارزش ارزش)قبل از محاسبه مشخصه وابسته ایجاد می شود gt2، و به همین ترتیب رویداد EVTبرای مقدار مجموعه 3 رخ نمی دهد. در پایان، وقتی دوباره 0 را تنظیم کردیم، محاسبه مشخصه gt2دیر است.

    به طور کلی، اثرات مشابه در الکترونیک آنالوگ و دیجیتال است: مسابقه سیگنال، برای مبارزه با آنها از تکنیک های مختلف استفاده می کنند. به طور خاص، همگام سازی. این کاری است که ما انجام خواهیم داد تا این کد به درستی کار کند:

    ImportControl.Aplicative((<$>)) وارد کردن FRP.Sodium main = do(sigClk, setVal)<- sync $ do -- Мы ввели новое событие clk -- сигнал синхронизации -- прям как в цифровой электронике (clk, sigClk) <- newEvent (val, setVal) <- newBehavior 0 -- Также вы создали альтернативную функцию -- получения значения по сигналу синхронизации -- и заменили все вызовы value на value" let value" = snapshot (\_ v ->v) clk اجازه دهید gt2 = (> 2)<$>val let evt = gate (value" val) gt2 listen (value" val) $ \v -> putStrLn $ "val = " ++ show v listen (value" gt2) $ \v -> putStrLn $ "val > 2 ? " ++ show v listen evt $ \v -> putStrLn $ "val > 2: " ++ show v return (sigClk, setVal) -- یک تابع همگام سازی جدید - که سیگنال همگام سازی را فراخوانی می کند - در پایان هر تراکنش - - و جایگزین آن همه تماس‌ها همگام‌سازی شود اجازه همگام‌سازی" a = همگام‌سازی $ a >> sigClk () همگام‌سازی" $ setVal 1 sync" $ setVal 2 sync" $ setVal 3 sync" $ setVal 4 sync" $ setVal 0

    اکنون خروجی ما مطابق انتظار است:

    λ> val اصلی = 0 val > 2 ? False val = 1 val > 2 ? False val = 2 val > 2 ? False val = 3 val > 2 ? True val > 2: 3 val = 4 val > 2 ? True val > 2: 4 val = 0 val > 2 ? نادرست

    تنبلی

    مشکلات از نوع متفاوت به ماهیت تنبلی محاسبات مربوط می شود هاسکل. این منجر به این واقعیت می شود که هنگام آزمایش کد در مفسر، برخی از خروجی ها در پایان ممکن است به سادگی از دست بروند. آنچه در این مورد می توان پیشنهاد داد انجام یک مرحله همگام سازی بی فایده در پایان است، مانند sync$return().

    نتیجه

    فکر می کنم فعلا همین کافی است. در حال حاضر یکی از نویسندگان کتابخانه است سدیمکتابی در مورد می نویسد FRP. امیدواریم این به نوعی خلأهای موجود در این زمینه برنامه نویسی را پر کند و در خدمت رایج کردن رویکردهای مترقی در ذهن متحجر ما باشد.