Затваряния в Ruby: Blocks, Procs и Lambdas - Каква е разликата?

Затварянията в Ruby могат да бъдат дефинирани като парчета код, които могат да бъдат предавани наоколо като обекти и могат да бъдат изпълнени по-късно. Трите различни начина, по които можете да създадете затваряне в Ruby е преминаване в блок към метод, създаване на прок и създаване на ламбда. Когато създадем затваряне в Ruby, това затваряне ще се свърже със заобикалящите го артефакти (като променливи, методи, обекти и т.н.), които са в обхвата към момента на създаване на затварянето. Ще разгледам различните видове затваряния в Ruby и ще обсъдя разликите между тях.

блокове

Блоковете могат да бъдат определени чрез do..end или {..} и могат да имат аргументи, както е показано по-долу:

Всеки метод в Ruby може да приеме незадължителен блок като неявен параметър, като в примера по-долу:

Така че това означава, че в примера по-горе, преминаваме в блок неявно към метода за поздрави и за да може блокът да бъде извикан, трябва да използваме ключовата дума за добив. Предаваме в локалната променлива човек (който сочи низ "Ashley", който беше предаван в метода поздрав като аргумент), за да се получи като аргумент, който се предава на името на параметъра на блока. Вътре в блока се поставя „Здравей # {name}!” Извежда Здравейте Ашли !. Сега нека видим какво се случва, когато не предадем аргумент на блока.

В горния пример не предаваме аргумент за получаване, което означава, че не се предава нито един аргумент към блока, който приема един параметър. Изненадващо, ArgumentError не беше върнат. Това е така, защото блоковете не налагат броене на аргументи. Вместо това, ако не предадем аргумент на блока, когато блокът приеме параметър, параметърът ще се позове на нула. Тъй като никой аргумент не е предаден на името на параметъра блок, името на локалната променлива сочи към нула, поради което изходът на метода поздрав беше Hello! което има допълнително място след Hello, където трябваше да е името.

PROCs

Има два начина за създаване на профили, както е показано по-долу:

В първия пример създаваме обект Proc, като извикваме новия метод на клас Proc и предаваме в блок като аргумент. Във втория пример можем да създадем proc чрез извикване на метода proc от модула на Kernel и да прехвърлим в блок като аргумент.

За да извикаме блока, трябва да се обадим на метода за повикване на proc обекта.

В горния пример наричаме метода за повикване в proc1, който сочи към обекта proc. След това „Ашли“ се предава като аргумент на метода за повикване, който се предава на името на параметъра на блока и Здравей Ашли! се отпечатва.

Примерът по-долу демонстрира какво се случва, когато не се предаваме в аргумент на процедурата.

Когато методът на повикване се извиква на обекта proc proc1, не предаваме аргумент. Това означава, че не се предава никакъв аргумент на името на блоковия параметър. Не ArgumentError е повдигнат, тъй като procs споделят същите правила за arity като блоковете и не налагат броене на аргументи. Точно като блоковете, ако не се предава нито един аргумент на параметъра, тогава този параметър ще бъде присвоен на нула. Това е причината да получим продукцията Здравейте !.

Lambdas

Има два начина за създаване на ламбда, както е показано по-долу:

В първия пример можем да създадем лямбда, като извикаме метода lambda от модула на Kernel и да предадем в блок като аргумент. Във втория пример създаваме ламбда, използвайки синтактичната захар на Руби. В примера, който използваме -> и след това поставяме блоковия параметър вътре в скобите и след това преминаваме в блока. Така че основната разлика между двата различни начина за създаване на ламбда е, че в първия пример блоковите параметри са вътре в блока, а във втория пример параметрите на блока са в скобите след -> вместо вътре в блока.

Едно нещо трябва да отбележим, че за да създадем лямбда, не можем да направим това:

Причината е така, защото ламбдите всъщност не са обекти на Ламбда, те са Proc обекти. Основната разлика между procs и lambdas е фактът, че те имат различни правила за arity.

Тъй като lambdas са Proc обекти, можем да извикаме метод за извикване на lambda и блокът ще бъде изпълнен. В първия пример по-горе, ние призоваваме метода за повикване на lambda1 и предаваме в аргумента "Ashley", който се предава на името на параметъра блок и поставя "Hello # {name}!" Здравейте Ашли !. Във втория пример, lambda1 се извиква, без да предава аргумент на метода за повикване и тъй като блокът приема един параметър, ArgumentError се връща. Това показва, че за разлика от procs и блокове, lambdas налагат броене на аргументи.

Последно нещо…

Друго нещо, което разделя блокове, прок и ламбда, е как те се справят с ключовата дума за връщане.

блокове

Когато пускам кода по-долу:

Получавам това съобщение за грешка:

неочаквано връщане (LocalJumpError)

Така че обикновено, когато методът отстъпва на блока, случващото се е, че изпълнението на програмата скача от прилагането на метода към блока. Като цяло програмите връщат LocalJumpError, когато имаме доходност при реализацията на метода, но никога не са преминали в блок към метода. Това кара изпълнението на програмата да скочи от внедряването на метода към несъществуващия блок, поради което възниква грешката. Нямаше блок, който да скочи, следователно грешката LocalJumpError!

Това е приблизително същата причина, поради която получихме LocalJumpError в горния пример. Когато методът отстъпи на блока, изпълнението на програмата прескочи до блока, където срещна ключовата дума за връщане. Това накара изпълнението на програмата да спре рязко, без да се върне към прилагането на метода, поради което LocalJumpError беше върнат.

PROCs

Когато пускам кода по-долу:

Не получавам никакви грешки, както направих в блоковия пример. След като процедурата се извиква и се връща „здравей“ се оценява вътре в блока, изпълнението на програмата спира и „здравей“ се връща от метода proc_example. Забележете, че последният ред на метода „довиждане“ никога не е оценяван. Причината за това е, защото procs се връщат от самия блок, което причинява спирането на изпълнението на метода.

Lambdas

Когато пускам кода по-долу:

Изпълнението на програмата не спря преждевременно. Когато се извика lambda1 и се извика блокът, изпълнението на програмата не спира и се връща отвътре в блока, както в примера на proc. Вместо това изпълнението на програмата продължава и „сбогом“ бе върнат чрез метода lambda_example. Така че, когато ключовата дума за връщане се оцени вътре в блока, лямбда ще игнорира ключовата дума за връщане и изпълнението на програмата продължава.

Затварянията в Ruby могат да бъдат трудни и ако съм пропуснал нещо в блога си относно закриването, не се колебайте да споделяте!