Закрыцці ў Ruby: Blocks, Procs і Lambdas - у чым розніца?

Закрыцці ў Ruby можна вызначыць як кавалкі кода, якія могуць быць перададзены як аб'екты, так і могуць быць выкананы ў больш позні час. Тры розныя спосабы стварэння закрыцця ў Ruby - гэта пераход у блок да метаду, стварэнне працэдуры і стварэнне лямбда. Калі мы ствараем закрыццё ў Ruby, гэта закрыццё прывяжа да яго навакольных артэфактаў (напрыклад, зменных, метадаў, аб'ектаў і г.д.), якія знаходзяцца ў аб'ёме на момант стварэння закрыцця. Я збіраюся прайсціся па розных тыпах закрыццяў у Рубі і абмяркуем розніцу паміж імі.

Блокі

Блокі могуць быць вызначаны па до..end або {..} і могуць мець аргументы, як паказана ніжэй:

Кожны метад у Ruby можа прыняць дадатковы блок як няяўны параметр, як у прыкладзе ніжэй:

Такім чынам, што гэта азначае, што ў прыведзеным вышэй прыкладзе мы перадаем у блоку няяўна метад прывітання, і для таго, каб выклікаць блок, нам трэба выкарыстоўваць ключавое слова даходнасці. Мы пераходзім у лакальную зменную асобу (якая паказвае на радок "Эшлі", якая была перададзена ў якасці аргумента метаду вітання), каб атрымаць аргумент, які перадаецца імя параметра блока. Усярэдзіне блока ставіцца “Прывітанне # {імя}!”. Зараз давайце паглядзім, што адбываецца, калі мы не перададзім аргумент блоку.

У прыведзеным вышэй прыкладзе мы не перадаём аргумент для атрымання вынікаў, а гэта азначае, што ні адзін аргумент не перадаецца блоку, які прымае адзін параметр. Дзіўна, але ArgumentError не вярнуўся. Гэта таму, што блокі не прымушаюць падлічваць аргументы. Замест гэтага, калі мы не перададзім аргумент блоку, калі блок прымае параметр, параметр будзе спасылацца на нуль. Паколькі ні адзін аргумент не быў перададзены імя параметра блока, імя лакальнай зменнай паказвае на нуль, таму выснова прывітальнага метаду быў Прывітанне! які мае дадатковую прастору пасля прывітання, дзе павінна быць назва.

Прац

Як паказана ніжэй, можна стварыць два спосабы стварэння дакументаў:

У першым прыкладзе мы ствараем аб'ект Proc, называючы новы метад класа Proc і перадаючы яго ў блоку ў якасці аргумента. У другім прыкладзе мы можам стварыць Proc, выклікаючы метад proc з модуля Kernel і перадаваць у блоку як аргумент.

Каб выклікаць блок, нам трэба выклікаць метад выкліку на аб'екце proc.

У прыведзеным вышэй прыкладзе мы называем метад выкліку на proc1, які паказвае на аб'ект proc. Затым «Ашлі» перадаецца ў якасці аргумента метаду выкліку, які перадаецца імя параметра блока і прывітанне Эшлі! друкуецца.

Прыклад ніжэй паказвае, што адбываецца, калі мы не перададзім аргумент прац.

Калі метад выкліку выкліканы на аб'ект proc proc1, мы не перадаем аргумент. Гэта азначае, што ні адзін аргумент не быў перададзены ў імя параметра блока. Ніякага ArgumentError не ўздымалася, бо procs падзяляюць тыя ж правілы arity, што і блокі, і не прымушаюць падлічваць аргументы. Гэтак жа, як і блокі, калі ніводны аргумент не перадаецца параметры, то гэты параметр будзе прызначаны нулю. Гэта прычына, чаму мы атрымліваем выхад Прывітанне !.

Лямбда

Ёсць два спосабы стварэння лямбда, як паказана ніжэй:

У першым прыкладзе мы можам стварыць лямбда, патэлефанаваўшы лямбда-метадам з модуля Kernel і перадаўшы ў блоку як аргумент. У другім прыкладзе мы ствараем лямбда, выкарыстоўваючы сінтаксічны цукар Рубі. У прыкладзе, які мы выкарыстоўваем ->, а потым размяшчаем параметр блока ў дужках, а потым пераходзім у блок. Таму галоўнае адрозненне двух розных спосабаў стварэння лямбда складаецца ў тым, што ў першым прыкладзе параметры блока знаходзяцца ўнутры блока, а ў другім прыкладзе параметры блока знаходзяцца ў дужках пасля ->, а не ўнутры блока.

Адно трэба адзначыць, што стварыць лямбда нельга гэтага:

Прычына таму, што лямбды на самай справе не з'яўляюцца аб'ектамі лямбда, яны - аб'екты Proc. Асноўнае адрозненне дакументаў ад лямбда ў тым, што яны маюць розныя правілы сумяшчальнасці.

Паколькі лямбды з'яўляюцца аб'ектамі Proc, мы можам выклікаць метад выкліку лямбда і блок будзе выкананы. У першым прыкладзе вышэй мы называем метад выкліку на lambda1 і перадаем у аргумент "Эшлі", які пераходзіць да імя параметра блока і ставіць "Прывітанне # {імя}!" выхад Прывітанне Эшлі !. У другім прыкладзе lambda1 выклікаецца, не перадаючы аргумент метаду выкліку, і паколькі блок прымае адзін параметр, ArgumentError вяртаецца. Гэта паказвае, што ў адрозненне ад прокаў і блокаў, лямбдаі прымушаюць лічыць аргумент.

Адна апошняя рэч ...

Іншая справа, якая адрознівае блокі, прок і лямбда, - гэта тое, як яны працуюць з ключавым словам вяртання.

Блокі

Калі я запускаю код ніжэй:

Я атрымліваю гэтае паведамленне пра памылку:

нечаканае вяртанне (LocalJumpError)

Такім чынам, звычайна, калі метад саступае блоку, тое, што адбываецца, заключаецца ў тым, што выкананне праграмы пераходзіць ад рэалізацыі метаду да блока. Увогуле, праграмы вяртаюць LocalJumpError, калі мы маем прыбытак у рэалізацыі метаду, але ніколі не пераходзім у блок да метаду. Гэта прымушае выкананне праграмы перайсці ад рэалізацыі метаду да неіснуючага блока, і таму ўзнікае памылка. Не было блока, каб перайсці, адсюль і памылка LocalJumpError!

Гэта прыкладна тая самая прычына, па якой у прыкладзе вышэй мы атрымалі LocalJumpError. Калі метад паддаўся блоку, выкананне праграмы пераскочыла да блока, дзе яно сутыкнулася з ключавым словам return. Гэта прымусіла выкананне праграмы рэзка спыніцца, не адскокваючы да рэалізацыі метаду, і таму быў вернуты LocalJumpError.

Прац

Калі я запускаю код ніжэй:

У мяне няма прыкладаў памылак, як у прыкладзе блока. Пасля таго, як выклік выкліку і ацэнка вяртання прывітанне ўнутры блока, выкананне праграмы спыняецца, і прывітанне вяртаецца з метаду proc_example. Звярніце ўвагу, што апошні радок метаду "бывай" ніколі не ацэньваўся. Прычынай гэтага з'яўляецца тое, што дакументы вяртаюцца з самога блока, выклікаючы спыненне выканання метаду.

Лямбда

Калі я запускаю код ніжэй:

Выкананне праграмы не спынілася заўчасна. Калі быў выкліканы lambda1 і выклік блока, выкананне праграмы не спынялася і вярталася з блока, як у прыкладзе proc. Замест гэтага, праграма працягваецца, і "да пабачэння" вярнуўся метадам lambda_example. Такім чынам, калі ключавое слова вяртання будзе ацэньвацца ўнутры блока, лямбда будзе ігнараваць ключавое слова вяртання і выкананне праграмы працягваецца.

Закрыцці ў Ruby могуць быць складанымі, і калі я прапусціў што-небудзь у сваім блогу пра закрыцці, можаце падзяліцца!