Кіріспе

Компьютерлік ғылымда funarg мәселесі (функция аргументі мәселесі) – функцияларды бірінші сыныптық объектілер ретінде бағдарламалау тілінің іске асырылуында жүзеге асыру кезінде туындайтын қиындықтарды білдіреді, осылайша функциялар үшін стекке негізделген жадты бөлуді пайдалану мүмкін болады. Бұл қиындық тек қана егер ішкі функцияның коды функция анықталған ортадағы идентификаторларға тікелей (яғни, аргументтер арқылы емес) сілтеме жасаса, бірақ функция шақырылған ортада емес, ғана туындайды. Стандартты шешім – мұндай сілтемелерге тыйым салу немесе жабуларды (closures) қолдану. Funarg мәселесінің екі аздап өзгешеленетін түрі бар. Funarg жоғары мәселесі функция шақырудан функцияны қайтарудан (немесе басқа жолмен "жоғары" жіберуден) туындайды. Funarg төмен мәселесі функцияны басқа функцияға параметр ретінде беруден туындайды.

Жоғарыға көтерілу

Функция басқа функцияны шақырғанда, шақырушы функцияның жергілікті күйі (параметрлер мен жергілікті айнымалыларды қоса) сақталуы керек, сонда шақырылған функция қайтқаннан кейін орындалу жалғасады. Көптеген компиляцияланған бағдарламаларда бұл жергілікті күй шақыру стегінде стек фреймі немесе белсендіру жазбасы деп аталатын деректер құрылымында сақталады. Бұл стек фреймі басқа функцияны шақыру алдында түртіледі немесе бөлінеді, ал шақырылған функция шақырушы функцияға қайтқанда ашылады немесе босатылады. Көтерілген funarg мәселесі шақырушы функция шақырылған/шығып кеткен функцияның күйіне функция қайтқаннан кейін сілтеме жасағанда туындайды. Сондықтан, шақырылған функцияның күй айнымалыларын қамтитын стек фреймін функция қайтқанда босатуға болмайды, өйткені бұл стекке негізделген функция шақыру парадигмасын бұзады. Көтерілген funarg мәселесін шешудің бір жолы – барлық белсендіру жазбаларын стек емес, үймеден бөлу және оларды қажет болмай қалғанда босату үшін қоқыс жинау немесе сілтемелерді санаудың қандай да бір түрін пайдалану. Үймеде белсендіру жазбаларын басқару тарихи түрде стектегіден тиімсіз деп есептелген (бірақ бұл толығымен дұрыс емес) және оның іске асыруы күрделі болып көрінеді. Типтік бағдарламалардың көпшілігі (функционалдық бағдарламалау тілдеріндегі бағдарламаларға қарағанда) көтерілген funarg-тар жасамайды, бұл олардың іске асырылуымен байланысты ықтимал қосымша шығындар туралы алаңдаушылықты арттырады. Сонымен қатар, бұл тәсіл қоқыс жинауды қолдамайтын тілдерде қиынға түседі. Кейбір тиімділікке бағытталған компиляторлар гибридтік тәсілді қолданады, онда функцияның белсендіру жазбалары, егер компилятор статикалық бағдарламалық талдау арқылы функция көтерілген funarg-тар жасамайтынын анықтаса, стекте бөлінеді. Әйтпесе, белсендіру жазбалары үймеде бөлінеді. Басқа шешім – жабылу жасалған кезде айнымалылардың мәнін жабылуға көшіру. Бұл өзгертілетін айнымалылар үшін әртүрлі мінез-құлыққа әкеледі, өйткені күй енді жабылулар арасында ортақ болмайды. Бірақ егер айнымалылар тұрақты екені белгілі болса, онда бұл тәсіл эквивалентті болады. ML тілдері осы тәсілді қолданады, өйткені осы тілдердегі айнымалылар мәндерге байланысты, яғни айнымалыларды өзгертуге болмайды. Java да анонимді сыныптарға (және Java 8-ден бастап lambda-ларға) қатысты осы тәсілді қолданады, ол тек шектеулі ауқымдағы нақты соңғы (яғни тұрақты) айнымалыларға сілтеме жасауға мүмкіндік береді. Кейбір тілдер бағдарламашыға екі мінез-құлықтың бірін нақты таңдауға мүмкіндік береді. PHP 5.3-тегі анонимді функциялар қандай айнымалыларды жабылуға қосу керектігін `use` сөйлемі арқылы көрсетуді талап етеді; егер айнымалы сілтеме арқылы тізімделген болса, ол бастапқы айнымалыға сілтеме береді; әйтпесе, ол мәнді береді. Apple's Blocks анонимді функцияларында жергілікті айнымалылар әдепкі бойынша мән бойынша түсіріледі; егер күйді жабылулар арасында немесе жабылу мен сыртқы ауқым арасында бөлісу қажет болса, айнымалы `block` модификаторымен жариялануы керек, бұл жағдайда бұл айнымалы үймеде бөлінеді.

Төменге қарай көшу проблемасы

Төменге қарай funarg функцияның іс жүзінде орындалмаған күйін де білдіре алады. Бірақ, анықтама бойынша, төменге қарай funarg-тың болуы оны жасайтын функцияның орындалуында қамтылғандықтан, функцияның стек фреймін көбінесе стекте сақтауға болады. Дегенмен, төменге қарай funarg-тардың болуы бағдарлама күйі туралы адам мен машинаның ойлауын қиындататын жабылулар мен стек фреймдердің ағаш тәрізді құрылымын білдіреді. Төменге қарай funarg мәселесі құйрық шақыруларды және жалғастыру стилінде жазылған кодты тиімді компиляциялауды қиындатады. Мұндай ерекше жағдайларда, бағдарламашының ниеті (көбінесе) функцияның шектеулі стек кеңістігінде жұмыс істеуі болады, сондықтан "жылдам" әрекет шын мәнінде қажет емес болуы мүмкін.

Практикалық әсері

Тарихи тұрғыдан алғанда, жоғары қарайғы funarg проблемасы көбінесе қиынға соқты. Мысалы, Паскаль бағдарламалау тілі функцияларды аргумент ретінде беруге мүмкіндік береді, бірақ нәтиже ретінде қайтаруға жол бермейді; сондықтан Паскальдің іске асырылымдары төменгі қарайғы funarg проблемасын шешуге міндетті, бірақ жоғары қарайғысын емес. Modula 2 және Oberon бағдарламалау тілдері (Паскальдің ұрпақтары) функцияларды параметрлер ретінде де, қайтарым мәндері ретінде де қолдануға рұқсат береді, бірақ тағайындалған функция ішкі функция болмауы мүмкін. C бағдарламалау тілі тарихи тұрғыдан функция анықтамаларын іштей орналастыруға жол бермеу арқылы funarg проблемасының негізгі қиындығынан қашады; өйткені әрбір функцияның ортасы бірдей болады, тек статикалық түрде бөлінген жаһандық айнымалылар мен функцияларды қамтиды, ал функция кодына сілтеме функцияны толыққанды сипаттайды. Apple C үшін жабылу синтаксисін ұсынды және іске қосты, ол қажет болған жағдайда жабылуларды стектен үймеге динамикалық түрде жылжыту арқылы жоғары қарайғы funarg проблемасын шешеді. Java бағдарламалау тілі анонимді ішкі және жергілікті сыныптардағы ішкі функциялар қолданатын контексті соңғы деп жариялауды, ал lambda өрнектерінде қолданылатын контексті тиімді түрде соңғы болуын талап ету арқылы осы мәселені шешеді. C# және D тілдерінде функция көрсеткіші мен байланысты айнымалыларды қоршап тұратын lambda-лар (жабылулар) бар. Функционалдық тілдерде функциялар – кез келген жерге жіберілуі мүмкін бірінші сыныптық мәндер. Осылайша, Scheme немесе Standard ML іске асырылымдары жоғары және төменгі қарайғы funarg проблемаларын екеуін де шешуі керек. Бұл әдетте функция мәндерін үймеге бөлінген жабылу ретінде көрсету арқылы жүзеге асырылады, бұрын сипатталғандай. OCaml компиляторы тиімділікті арттыру үшін гибридтік әдіс қолданады (статикалық бағдарламалық талдау негізінде).