Бағдарламалық жасақтама саласында үшкір басты ғалымдар мен олар сияқты мықты тағы бір күш — шашы тікірейген бастықтар (pointy-haired boss) арасында толассыз күрес жүріп жатыр. Шашы тікірейген бастықтың кім екенін бәрі біледі, солай емес пе? Меніңше, технология әлеміндегі адамдардың көбі бұл мультфильм кейіпкерін танып қана қоймай, өз компаниясындағы осы бейнеге негіз болған нақты адамды да жақсы біледі.
Шашы тікірейген бастық өздігінен жиі кездесетін, бірақ бірге сирек ұшырасатын екі қасиетті керемет үйлестіреді: (а) ол технология туралы мүлде ештеңе білмейді және (б) ол туралы өте берік пікірде болады.
Мысалы, сізге қандай да бір бағдарлама жазу керек болды делік. Шашы тікірейген бастық ол бағдарламаның қалай жұмыс істеуі керектігін мүлде түсінбейді және бір бағдарламалау тілін екіншісінен ажырата алмайды, бірақ соған қарамастан оны қай тілде жазу керектігін біліп отырады. Дәл солай. Ол сізді оны Java тілінде жазуы керек деп есептейді.
Ол неге бұлай ойлайды? Кәне, шашы тікірейген бастықтың миына көз жүгіртейік. Оның ойы шамамен мынадай: Java — бұл стандарт. Мен бұған сенімдімін, өйткені бұл туралы баспасөзден үнемі оқып тұрамын. Ол стандарт болғандықтан, оны қолданғаным үшін басым бәлеге қалмайды. Сондай-ақ, бұл дегеніміз — Java бағдарламашылары әрқашан көп болады, сондықтан қазіргі бағдарламашыларым жұмыстан кетіп қалса да (ал олар менде жұмыс істегенде белгісіз себептермен үнемі солай істейді), олардың орнын оңай таба аламын.
Бұл бір қарағанда қисынсыз көрінбейді. Бірақ мұның бәрі айтылмаған бір болжамға негізделген және ол болжам қате болып шығады. Шашы тікірейген бастық барлық бағдарламалау тілдері шамамен бірдей деп сенеді. Егер бұл рас болса, ол дәл табар еді. Егер тілдердің бәрі бірдей болса, әрине, қалғандардың бәрі қолданып жатқан тілді пайдаланыңыз.
Бірақ тілдердің бәрі бірдей емес және мен мұны сізге олардың арасындағы айырмашылықтарға тоқталмай-ақ дәлелдей аламын деп ойлаймын. Егер 1992 жылы шашы тікірейген бастықтан бағдарламалық жасақтаманы қай тілде жазу керек деп сұрасаңыз, ол бүгінгідей еш күмәнданбастан жауап берер еді: бағдарламалық жасақтама C++ тілінде жазылуы керек. Бірақ егер тілдердің бәрі бірдей болса, шашы тікірейген бастықтың пікірі неге өзгеруі керек? Тіпті, Java жасаушыларына жаңа тіл ойлап табудың не қажеті бар еді?
Әдетте, жаңа тіл жасасаңыз, оны адамдарда бұрыннан бар нәрседен қандай да бір жағынан жақсырақ деп есептегендіктен жасайсыз. Шынында да, Гослинг Java-ның алғашқы ақ парағында (white paper) бұл тіл C++ кемшіліктерін түзету үшін жасалғанын анық айтады. Міне, көрдіңіз бе: тілдердің бәрі бірдей емес. Егер сіз шашы тікірейген бастықтың ойынан Java-ға, содан кейін Java тарихы арқылы оның бастауына көз жүгіртсеңіз, бастапқы болжамыңызға қайшы келетін тұжырымға тап боласыз.
Сонымен, кімдікі дұрыс? Джеймс Гослинг пе, әлде шашы тікірейген бастық па? Әрине, Гослингтікі дұрыс. Кейбір тілдер белгілі бір есептер үшін басқаларынан шынымен де жақсырақ. Ал бұл, байқап отырғаныңыздай, бірнеше қызықты сұрақтарды тудырады. Java белгілі бір мәселелер үшін C++-тен жақсырақ болу үшін жасалған. Қандай мәселелер? Қай кезде Java жақсырақ, ал қай кезде C++? Екеуінен де жақсырақ басқа тілдер болатын жағдайлар бар ма?
Бұл сұрақ туралы ойлана бастасаңыз, нағыз былықтың бетін ашасыз. Егер шашы тікірейген бастық бұл мәселенің барлық күрделілігі туралы ойлануға мәжбүр болса, оның басы жарылып кетер еді. Ол барлық тілдерді бірдей деп санағанша, оған тек ең танымал болып көрінетінін таңдау жеткілікті, ал бұл технологиядан гөрі сән мәселесі болғандықтан, тіпті ол да дұрыс жауапты таба алады. Бірақ егер тілдер әртүрлі болса, ол өзі мүлдем түсінбейтін екі нәрсенің арасындағы оңтайлы тепе-теңдікті табуға тырысып, бір мезгілде екі теңдеуді шешуге мәжбүр болады: өзі шешуі тиіс мәселеге шамамен жиырма шақты жетекші тілдің қайсысы сәйкес келетіндігі және олардың әрқайсысы үшін бағдарламашылар, кітапханалар және т.б. табу мүмкіндігі. Есіктің арғы жағында осындай нәрсе күтіп тұрса, шашы тікірейген бастықтың оны ашқысы келмейтіні таңқаларлық емес.
Барлық бағдарламалау тілдері бірдей деп сенудің кемшілігі — оның шындыққа жанаспайтындығында. Бірақ артықшылығы — ол сіздің өміріңізді айтарлықтай жеңілдетеді. Менің ойымша, бұл идеяның соншалықты кең таралғанының басты себебі де осы. Бұл — ыңғайлы идея.
Біз Java өте жақсы болуы керек деп білеміз, өйткені ол сәнді, жаңа бағдарламалау тілі. Шынымен солай ма? Егер бағдарламалау тілдері әлеміне сырттай қарасаңыз, Java ең соңғы жаңалық сияқты көрінеді. (Жеткілікті қашықтықтан қарағанда Sun төлеген үлкен, жарқыраған билбордтан басқа ештеңе көрінбейді.) Бірақ бұл әлемге жақынырақ қарасаңыз, сәнділіктің де деңгейлері бар екенін байқайсыз. Хакерлер субмәдениетінде Java-дан әлдеқайда сәнді саналатын Perl деген басқа тіл бар. Мысалы, Slashdot Perl арқылы жасалған. Ол жігіттердің Java Server Pages қолданып жатқанын көре алмайсыз. Бірақ Python деп аталатын тағы бір жаңа тіл бар, оны пайдаланушылар Perl-ге төмен қарауға бейім және өз кезегін күтіп тұрғандар тағы да бар.
Егер бұл тілдерді ретімен қарасаңыз — Java, Perl, Python — қызықты бір заңдылықты байқайсыз. Кем дегенде, егер сіз Lisp хакері болсаңыз, бұл заңдылықты бірден аңғарасыз. Олардың әрқайсысы бірте-бірте Lisp-ке көбірек ұқсай бастайды. Python тіпті көптеген Lisp хакерлері қателік деп санайтын мүмкіндіктерді де көшіріп алған. Сіз қарапайым Lisp бағдарламаларын Python тіліне жолма-жол аудара аласыз. Қазір 2002 жыл, ал бағдарламалау тілдері 1958 жылғы деңгейге енді ғана жақындап қалды.
Математиканы қуып жету
Менің айтайын дегенім, Lisp-ті 1958 жылы Джон Маккарти ашқан, ал танымал бағдарламалау тілдері оның сол кезде жасаған идеяларына енді ғана жетіп жатыр.
Бұл қалайша рас болуы мүмкін? Компьютерлік технологиялар өте жылдам өзгеретін нәрсе емес пе? Айтайын дегенім, 1958 жылы компьютерлер тоңазытқыш көлеміндей, ал есептеу қуаты қол сағатындай ғана болатын дәуір еді. Мұндай ескі технология ең соңғы жетістіктерден асып түсуді былай қойғанда, бүгінгі күнге қалайша қатысты бола алады?
Мен сізге себебін айтайын. Себебі Lisp шын мәнінде бағдарламалау тілі болу үшін жасалмаған, кем дегенде бүгінгі біз түсінетін мағынада. Біз бағдарламалау тілі деп компьютерге не істеу керектігін айту үшін қолданатын құралды айтамыз. Маккарти кейінірек осы мағынадағы бағдарламалау тілін жасауды көздегенімен, түптеп келгенде біз қол жеткізген Lisp оның теориялық жаттығу ретінде жасаған бөлек жұмысына — Тьюринг машинасына неғұрлым ыңғайлы балама табу талпынысына негізделген еді. Маккарти кейінірек айтқандай,
Lisp-тің Тьюринг машиналарынан гөрі ұқыптырақ екенін көрсетудің тағы бір жолы — әмбебап Lisp функциясын жазып, оның әмбебап Тьюринг машинасының сипаттамасынан қысқарақ әрі түсініктірек екенін дәлелдеу болды. Бұл Lisp өрнегінің мәнін есептейтін eval... атты Lisp функциясы еді.... eval функциясын жазу үшін Lisp функцияларын Lisp деректері ретінде көрсететін нотацияны ойлап табу қажет болды, және мұндай нотация мақаланың мақсаты үшін ғана ойластырылды, оның тәжірибеде Lisp бағдарламаларын көрсету үшін қолданылатыны мүлдем ойға келмеген еді.
Бұдан кейін не болды десеңіз, 1958 жылдың аяғында Маккартидің аспиранттарының бірі Стив Рассел eval-дың осы анықтамасына қарап, егер оны машиналық тілге аударса, нәтижесінде Lisp интерпретаторы шығатынын түсінді.
Ол кезде бұл үлкен тосынсый болды. Маккарти кейінірек сұхбатында бұл туралы былай деді:
Стив Рассел: «Қараңызшы, мен неге осы eval-ды бағдарламалап көрмеске...» деді, ал мен оған: «Ой-хой, сіз теория мен практиканы шатастырып отырсыз, бұл eval есептеу үшін емес, оқу үшін арналған» дедім. Бірақ ол өз дегенінен қайтпай, оны жасап шықты. Яғни ол менің мақаламдағы eval-ды қателерін түзете отырып, [IBM] 704 машиналық кодына компиляциялады және содан кейін мұны Lisp интерпретаторы деп жариялады, ол шынында да солай еді. Сөйтіп, сол сәтте Lisp негізінен бүгінгі күндегі пішініне ие болды....
Кенеттен, бірнеше аптаның ішінде Маккарти өзінің теориялық жаттығуы нақты бағдарламалау тіліне — тіпті ол ойлағаннан да күштірек тілге айналғанын көрді.
Сонымен, бұл 1950 жылдардағы тілдің ескірмеуінің қысқаша түсіндірмесі мынада: ол технология емес, математика болатын, ал математика ешқашан ескірмейді. Lisp-ті 1950 жылдардағы аппараттық жасақтамамен емес, мәселен, 1960 жылы ашылған және әлі күнге дейін жалпы мақсаттағы ең жылдам сұрыптау алгоритмі болып табылатын Quicksort алгоритмімен салыстырған жөн.
1950 жылдардан бері сақталып келе жатқан тағы бір тіл бар, ол — Fortran, және ол тіл дизайнына мүлдем қарама-қарсы көзқарасты білдіреді. Lisp кенеттен бағдарламалау тіліне айналып кеткен теория болса, Fortran арнайы бағдарламалау тілі ретінде жасалған, бірақ қазіргі көзқараспен қарағанда өте төмен деңгейлі тіл еді.
1956 жылы жасалған Fortran I тілі бүгінгі Fortran-нан мүлде басқа дүние еді. Fortran I негізінен математикасы бар ассамблер тілі болатын. Кейбір жағынан ол кейінгі ассамблер тілдерінен де әлсіз еді; мысалы, ішкі бағдарламалар (subroutines) болмады, тек өтулер (branches) ғана бар еді. Қазіргі Fortran, бәлкім, Fortran I-ге қарағанда Lisp-ке жақынырақ деуге болады.
Lisp пен Fortran — бірі математикаға, екіншісі машина архитектурасына негізделген екі бөлек эволюциялық ағаштың діңі болды. Бұл екі ағаш содан бері бір-біріне жақындап келеді. Lisp бастапқыда қуатты болып басталып, келесі жиырма жыл ішінде жылдамдыққа ие болды. Негізгі ағымдағы (mainstream) тілдер жылдам болып басталып, кейінгі қырық жыл ішінде бірте-бірте қуатты бола түсті, сөйтіп бүгінде олардың ең озықтары Lisp-ке едәуір жақындап қалды. Жақын, бірақ оларға әлі де бірнеше нәрсе жетіспейді....
Lisp-ті ерекше еткен не?
Алғаш жасалған кезде Lisp тоғыз жаңа идеяны қамтыды. Олардың кейбіреулерін біз қазір әдеттегідей қабылдаймыз, басқалары тек озық тілдерде ғана кездеседі, ал екеуі әлі күнге дейін тек Lisp-ке ғана тән. Бұл тоғыз идея негізгі ағымға ену реті бойынша мыналар: Шартты операторлар (Conditionals). Шартты оператор — бұл if-then-else конструкциясы. Біз мұны қазір қалыпты жағдай деп санаймыз, бірақ Fortran I-де бұл болмаған. Онда тек негізгі машиналық нұсқауға тығыз байланысты шартты goto ғана болды.
Функция типі. Lisp-те функциялар бүтін сандар немесе жолдар сияқты дербес деректер типі болып табылады. Олардың литералды сипаттамасы бар, оларды айнымалыларда сақтауға, аргумент ретінде беруге және т.б. болады.
Рекурсия. Lisp оны қолдаған алғашқы бағдарламалау тілі болды.
Динамикалық типтеу. Lisp-те барлық айнымалылар іс жүзінде көрсеткіштер (pointers) болып табылады. Типтер айнымалыларға емес, мәндерге тиесілі, ал айнымалыларды тағайындау немесе байланыстыру — олар сілтеп тұрған нәрсені емес, көрсеткіштерді көшіруді білдіреді.
Қоқыс жинау (Garbage collection).
Өрнектерден құралған бағдарламалар. Lisp бағдарламалары — әрқайсысы мән қайтаратын өрнектер ағашы. Бұл өрнектер (expressions) мен нұсқауларды (statements) ажырататын Fortran мен одан кейінгі көптеген тілдерге қарама-қайшы келеді.
Бұл айырмашылықтың Fortran I-де болуы табиғи еді, өйткені онда нұсқауларды бір-бірінің ішіне кірістіруге (nest) болмайтын. Сондықтан математиканың жұмыс істеуі үшін өрнектер қажет болғанымен, басқа ештеңенің мән қайтаруының мәні болмады, өйткені ол мәнді күтіп алатын ештеңе жоқ еді.
Бұл шектеу блоктық құрылымды тілдердің пайда болуымен жойылды, бірақ ол кезде бәрі кеш еді. Өрнектер мен нұсқаулар арасындағы айырмашылық бекіп қалған болатын. Ол Fortran-нан Algol-ға, одан кейін олардың екеуінің де ұрпақтарына таралды.
Символ типі. Символдар іс жүзінде хэш-кестеде сақталған жолдарға сілтейтін көрсеткіштер болып табылады. Сондықтан әрбір таңбаны салыстырудың орнына көрсеткішті салыстыру арқылы теңдікті тексеруге болады.
Символдар мен тұрақтылар ағашын пайдаланатын кодқа арналған нотация.
Тілдің барлық құралдарының кез келген уақытта қолжетімді болуы. Оқу уақыты (read-time), компиляция уақыты (compile-time) және орындалу уақыты (runtime) арасында нақты айырмашылық жоқ. Сіз оқу кезінде кодты компиляциялай аласыз немесе іске қоса аласыз, компиляция кезінде кодты оқи аласыз немесе іске қоса аласыз, ал орындалу кезінде кодты оқи аласыз немесе компиляциялай аласыз.
Кодты оқу кезінде іске қосу пайдаланушыларға Lisp синтаксисін қайта бағдарламалауға мүмкіндік береді; кодты компиляция кезінде іске қосу — макростардың негізі; орындалу кезінде компиляция жасау — Lisp-тің Emacs сияқты бағдарламаларда кеңейту тілі ретінде қолданылуының негізі; ал орындалу кезінде оқу бағдарламаларға s-өрнектер арқылы өзара байланысуға мүмкіндік береді, бұл идея жақында XML түрінде қайта ашылды. Lisp алғаш пайда болған кезде бұл идеялар сол 1950 жылдардың соңындағы аппараттық жасақтама талаптарынан туындаған қарапайым бағдарламалау практикасынан тым алыс еді. Уақыт өте келе, бірқатар танымал тілдер арқылы көрініс тапқан негізгі тіл біртіндеп Lisp бағытында дамыды. 1-5 идеялар қазір кеңінен таралған. 6-шысы енді негізгі ағымда пайда бола бастады. Python-да 7-нің бір түрі бар, бірақ оған арналған нақты синтаксис жоқ сияқты.
Ал 8-шіге келетін болсақ, бұл олардың ішіндегі ең қызықтысы болуы мүмкін. 8 және 9 идеялар Lisp-ке кездейсоқ енді, өйткені Стив Рассел Маккарти ешқашан жүзеге асыруды жоспарламаған нәрсені іске асырып қойды. Дегенмен бұл идеялар Lisp-тің біртүрлі көрінісіне де, оның ең ерекше мүмкіндіктеріне де себепші болды. Lisp біртүрлі синтаксисі болғандықтан емес, оның мүлде синтаксисі болмағандықтан біртүрлі болып көрінеді; сіз бағдарламаларды басқа тілдер талданғанда артқы планда құрылатын синтаксистік талдау ағаштарында (parse trees) тікелей жазасыз, ал бұл ағаштар Lisp деректер құрылымы болып табылатын тізімдерден құралады.
Тілді өз деректер құрылымында көрсету өте қуатты мүмкіндік болып шықты. 8 және 9 идеялар бірге бағдарлама жазатын бағдарламаларды жазуға мүмкіндік береді дегенді білдіреді. Бұл оғаш идея болып көрінуі мүмкін, бірақ Lisp үшін бұл күнделікті әдеттегі нәрсе. Мұны істеудің ең кең таралған тәсілі — макрос деп аталатын механизм.
Lisp-тегі «макрос» термині басқа тілдердегі мағынаны білдірмейді. Lisp макросы қысқартудан бастап жаңа тілге арналған компиляторға дейінгі кез келген нәрсе болуы мүмкін. Егер сіз Lisp-ті шынымен түсінгіңіз келсе немесе жай ғана бағдарламалау көкжиегіңізді кеңейткіңіз келсе, макростар туралы көбірек білуге кеңес берер едім.
Макростар (Lisp мағынасында), менің білуімше, әлі күнге дейін тек Lisp-ке ғана тән. Бұл ішінара макростарға ие болу үшін тіліңізді Lisp сияқты біртүрлі етіп көрсетуге мәжбүр болатыныңызға байланысты. Бұл сондай-ақ, егер сіз осы соңғы қуат деңгейін қоссаңыз, енді жаңа тіл ойлап таптым деп айта алмайсыз, тек Lisp-тің жаңа диалектісін жасадым деп қана айта аласыз дегенге байланысты болуы мүмкін.
Мен мұны көбіне әзіл ретінде айтып отырмын, бірақ бұл шындыққа өте жақын. Егер сіз car, cdr, cons, quote, cond, atom, eq және тізімдер түрінде көрсетілген функцияларға арналған нотациясы бар тілді анықтасаңыз, онда сіз одан Lisp-тің қалған барлық бөлігін құрастыра аласыз. Шын мәнінде бұл Lisp-тің анықтаушы қасиеті: Маккарти Lisp-ке дәл осыны жүзеге асыру үшін осындай пішін берген еді.
Тілдердің маңызы қай жерде?
Сонымен, Lisp негізгі ағымдағы тілдер асимптоталық түрде жақындап келе жатқан белгілі бір шекті білдіреді делік — бұл бағдарламалық жасақтама жазу үшін оны нақты қолдану керек дегенді білдіре ме? Азырақ қуатты тілді қолдану арқылы қаншалықты ұтыласыз? Кейде инновацияның дәл алдыңғы шебінде болмау ақылға қонымдырақ емес пе? Және танымалдық белгілі бір дәрежеде өзін-өзі ақтамай ма? Мысалы, шашы тікірейген бастықтың бағдарламашыларды оңай жалдауға болатын тілді қолданғысы келуі дұрыс емес пе?
Әрине, бағдарламалау тілін таңдау айтарлықтай маңызды болмайтын жобалар да бар. Әдетте, қолданба неғұрлым күрделі болса, қуатты тілді қолданудан соғұрлым көп артықшылық көресіз. Бірақ көптеген жобалар мүлде күрделі емес. Бағдарламалаудың басым бөлігі кішігірім байланыстырушы бағдарламаларды (glue programs) жазудан тұратын шығар, ал шағын байланыстырушы бағдарламалар үшін өзіңізге бұрыннан таныс және қажетті нәрсеге арналған жақсы кітапханалары бар кез келген тілді қолдана аласыз. Егер бір Windows қолданбасынан екіншісіне деректерді беру ғана қажет болса, әрине, Visual Basic қолданыңыз.
Lisp-те де шағын байланыстырушы бағдарламаларды жазуға болады (мен оны үстел калькуляторы ретінде қолданамын), бірақ Lisp сияқты тілдердің ең үлкен ұтысы спектрдің екінші жағында — қатал бәсекелестік жағдайында қиын мәселелерді шешу үшін күрделі бағдарламалар жазу қажет болған кезде көрінеді. Жақсы мысал — ITA Software Orbitz компаниясына лицензиялаған әуе билеттерін іздеу бағдарламасы. Бұл жігіттер бұрыннан Travelocity және Expedia сияқты екі ірі, мықты бәсекелестер үстемдік еткен нарыққа кіріп, оларды технологиялық тұрғыдан жай ғана шаң қаптырған сияқты.
ITA қолданбасының өзегі — 200 000 жолдан тұратын Common Lisp бағдарламасы, ол әлі күнге дейін мейнфреймдер дәуірінің бағдарламалау тәсілдерін қолданып жүрген бәсекелестеріне қарағанда мүмкіндіктерді бірнеше есе көп іздейді. (Дегенмен, белгілі бір мағынада ITA да мейнфрейм дәуірінің бағдарламалау тілін қолданып отыр). Мен ITA кодтарын ешқашан көрген емеспін, бірақ олардың жетекші хакерлерінің бірінің айтуынша, олар макростарды көп қолданады және мұны естігеніме таңғалмаймын.
Орталыққа тартқыш күштер
Мен сирек кездесетін технологияларды пайдаланудың еш шығыны жоқ деп айтып отырған жоқпын. Шашы тікірейген бастықтың бұған алаңдауы толығымен қате емес. Бірақ ол тәуекелдерді түсінбегендіктен, оларды тым асырып бағалауға бейім.
Сирек қолданылатын тілдерді пайдаланудан туындауы мүмкін үш мәселені көріп тұрмын. Бағдарламаларыңыз басқа тілдерде жазылған бағдарламалармен жақсы жұмыс істемеуі мүмкін. Сіздің игілігіңізде кітапханалар аз болуы мүмкін. Және сізге бағдарламашыларды жалдау қиынға соғуы мүмкін.
Осылардың әрқайсысы қаншалықты үлкен мәселе? Біріншісінің маңыздылығы сіздің бүкіл жүйені бақылауыңызға байланысты өзгереді. Егер сіз қателіктері көп, жабық операциялық жүйеде (атын атамай-ақ қояйын) қашықтағы пайдаланушының компьютерінде жұмыс істеуі керек бағдарламалық жасақтама жазып жатсаңыз, қолданбаңызды ОЖ-мен бірдей тілде жазудың артықшылықтары болуы мүмкін. Бірақ егер сіз бүкіл жүйені бақылап отырсаңыз және барлық бөлшектердің бастапқы кодына қол жеткізе алсаңыз, ITA сияқты, өзіңіз қалаған кез келген тілді қолдана аласыз. Егер қандай да бір үйлесімсіздік туындаса, оны өзіңіз жөндей аласыз.
Серверлік қолданбаларда ең озық технологияларды қолдану арқылы ұтуға болады және бұл Джонатан Эриксон «бағдарламалау тілдерінің қайта өрлеу дәуірі» деп атайтын құбылыстың басты себебі деп ойлаймын. Сондықтан біз Perl және Python сияқты жаңа тілдер туралы естіп жатырмыз. Біз бұл тілдер туралы адамдар оларды Windows қосымшаларын жазу үшін қолданғандықтан емес, серверлерде қолданғандықтан естіп отырмыз. Бағдарламалық жасақтама жұмыс үстелінен серверлерге ауысқан сайын (бұл болашақпен тіпті Microsoft та келіскен сияқты), ортаңқол технологияларды қолдануға деген қысым барған сайын азаяды.
Кітапханаларға келетін болсақ, олардың маңыздылығы да қолданбаға байланысты. Қарапайым тапсырмалар үшін кітапханалардың қолжетімділігі тілдің ішкі күшінен асып түсуі мүмкін. Өзін-өзі ақтау шегі қайда? Дәл айту қиын, бірақ ол қай жерде болса да, кәдімгі дербес қолданба деп атауға болатын деңгейге жетпейді. Егер компания өзін бағдарламалық жасақтама саласында деп есептесе және олар өз өнімдерінің бірі болатын қосымшаны жазып жатса, онда оған бірнеше хакер тартылып, жазуға кем дегенде алты ай кететін шығар. Мұндай көлемдегі жобада қуатты тілдер бұрыннан бар кітапханалардың ыңғайлылығынан басым түсе бастайды.
Шашы тікірейген бастықтың үшінші алаңы — бағдарламашыларды жалдау қиындығы, меніңше, бұл шындыққа жанаспайтын жалған қауіп. Ақыр соңында, қанша хакер жалдауыңыз керек? Қазіргі кезде бағдарламалық жасақтаманы он адамнан аз командалар ең жақсы жасайтынын бәріміз білеміз ғой. Ал біреу естіген кез келген тіл бойынша осындай көлемде хакерлерді жалдауда қиындық көрмеуіңіз керек. Егер он Lisp хакерін таба алмасаңыз, онда сіздің компанияңыз бағдарламалық жасақтама жасау үшін дұрыс емес қалада орналасқан болуы мүмкін.
Шындығында, неғұрлым қуатты тілді таңдау сізге қажет команданың көлемін азайтуы мүмкін, өйткені (а) егер сіз неғұрлым қуатты тілді қолдансаңыз, сізге сонша хакер қажет болмауы мүмкін және (б) неғұрлым озық тілдерде жұмыс істейтін хакерлер ақылдырақ болуы әбден ықтимал.
Мен «стандартты» деп қабылданған технологияларды пайдалануға қысым болмайды деп айтып отырған жоқпын. Viaweb-те (қазіргі Yahoo Store) біз Lisp қолдану арқылы венчурлық инвесторлар мен әлеуетті сатып алушыларды таңғалдырған едік. Бірақ біз оларды Sun сияқты «өндірістік деңгейдегі» серверлердің орнына қарапайым Intel қораптарын сервер ретінде пайдаланғанымыз үшін, Windows NT сияқты нақты коммерциялық ОЖ орнына FreeBSD деп аталатын сол кезде белгісіз ашық бастапқы кодты Unix нұсқасын қолданғанымыз үшін, қазір ешкім тіпті есіне де алмайтын SET деп аталатын электрондық коммерция стандартын елемегеніміз үшін және тағы басқалар үшін де таңғалдырдық.
Костюм киген басшылардың техникалық шешімдерді сіздің орныңызға қабылдауына жол беруге болмайды. Lisp қолданғанымыз кейбір әлеуетті сатып алушыларды шошытты ма? Кейбіреулерін сәл ғана, бірақ егер біз Lisp қолданбағанда, олардың бізді сатып алғысы келетін бағдарламалық жасақтамасын жаза алмас едік. Оларға аномалия болып көрінген нәрсе шын мәнінде себеп пен салдар болатын.
Егер стартап бастасаңыз, өніміңізді венчурлық инвесторларға немесе әлеуетті сатып алушыларға ұнайтындай етіп жасамаңыз. Өніміңізді пайдаланушыларға ұнайтындай етіп жасаңыз. Егер пайдаланушылардың көңілінен шықсаңыз, қалғанының бәрі өздігінен келеді. Ал егер олай болмаса, сіз таңдаған технологиялардың қаншалықты қауіпсіз әрі стандартты болғаны ешкімді қызықтырмайды.
Ортаңқол болудың құны
Азырақ қуатты тілді қолдану арқылы қаншалықты ұтыласыз? Бұл туралы нақты деректер бар.
Қуаттылықты өлшеудің ең ыңғайлы өлшемі, бәлкім, код көлемі шығар. Жоғары деңгейлі тілдердің мәні — сізге үлкенірек абстракцияларды беру — белгілі бір өлшемдегі қабырғаны тұрғызу үшін көп қажет болмайтындай үлкен кірпіштер беру іспетті. Сондықтан тіл неғұрлым қуатты болса, бағдарлама соғұрлым қысқа болады (әрине, тек таңбалар саны бойынша ғана емес, жеке элементтер бойынша да).
Неғұрлым қуатты тіл қысқа бағдарламалар жазуға қалай мүмкіндік береді? Егер тіл мүмкіндік берсе, қолдануға болатын әдістердің бірі — төменнен жоғары қарай бағдарламалау (bottom-up programming). Қолданбаңызды жай ғана базалық тілде жазудың орнына, сіз базалық тілдің үстіне өзіңіздікіндей бағдарламаларды жазуға арналған тіл құрасыз, содан кейін бағдарламаңызды сол тілде жазасыз. Біріктірілген код бүкіл бағдарламаны базалық тілде жазғаннан әлдеқайда қысқа болуы мүмкін — шын мәнінде, көптеген қысу алгоритмдері осылай жұмыс істейді. Төменнен жоғары қарай жазылған бағдарламаны өзгерту де оңайырақ болуы керек, өйткені көптеген жағдайларда тіл деңгейін мүлде өзгерту қажет болмайды.
Кодтың көлемі маңызды, өйткені бағдарламаны жазуға кететін уақыт көбінесе оның ұзындығына байланысты. Егер сіздің бағдарламаңыз басқа тілде үш есе ұзағырақ болса, оны жазуға үш есе көп уақыт кетеді — және сіз көбірек адам жалдау арқылы бұл мәселені шеше алмайсыз, өйткені белгілі бір көлемнен асып кеткенде, жаңа қызметкерлер іс жүзінде тек таза шығынға айналады. Фред Брукс бұл құбылысты өзінің әйгілі The Mythical Man-Month атты кітабында сипаттаған және мен көргендердің бәрі оның айтқанын растай түсті.
Сонымен, егер сіз бағдарламаларыңызды Lisp тілінде жазсаңыз, олар қаншалықты қысқа болады? Мысалы, Lisp пен C тілін салыстырғанда мен естіген сандардың көбі 7-10 есе шамасында болды. Бірақ ITA туралы жақында New Architect журналында жарияланған мақалада «Lisp-тің бір жолы C-ның 20 жолын алмастыра алады» делінген және бұл мақала ITA президентінің дәйексөздеріне толы болғандықтан, олар бұл санды ITA-дан алған деп ойлаймын. Егер солай болса, біз бұған белгілі бір деңгейде сене аламыз; ITA бағдарламалық жасақтамасы Lisp-пен қатар C және C++ кодтарын да көп қамтиды, сондықтан олар тәжірибеге сүйеніп сөйлеп отыр.
Менің ойымша, бұл еселіктер тіпті тұрақты да емес. Олар күрделірек мәселелерге тап болғанда, сондай-ақ ақылдырақ бағдарламашыларыңыз болған кезде арта түседі деп ойлаймын. Шынымен мықты хакер жақсырақ құралдардан әлдеқайда көп нәтиже сығып ала алады.
Қалай болғанда да, осы қисық сызықтағы бір дерек нүктесі ретінде қарасақ: егер сіз ITA-мен бәсекелесетін болып, бағдарламалық жасақтамаңызды C тілінде жазуды таңдасаңыз, олар бағдарламалық жасақтаманы сізден жиырма есе жылдам жасай алар еді. Егер сіз жаңа мүмкіндікке бір жыл жұмсасаңыз, олар оны үш аптаға жетпейтін уақыт ішінде қайталай алар еді. Ал егер олар жаңа нәрсені жасауға небәрі үш ай жұмсаса, оған сіздің қол жеткізуіңізге бес жыл уақыт кетер еді.
Және не білесіз бе? Бұл – ең жақсы сценарий. Код көлемінің арақатынасы туралы айтқан кезде, сіз бағдарламаны әлсіз тілде шынымен жаза аласыз деп үнсіз болжайсыз. Бірақ шын мәнінде бағдарламашылардың қолынан келетін нәрсенің шегі бар. Егер сіз тым төмен деңгейлі тілмен күрделі мәселені шешуге тырыссаңыз, бір уақытта басыңызда сақтауға тым көп нәрсе жиналатын шекке жетесіз.
Сондықтан ITA-ның үш айда Lisp тілінде жаза алатын нәрсесін қайталау үшін оның ойдан шығарылған бәсекелесіне бес жыл қажет болады дегенімде, ештеңе дұрыс болмай қалмаса ғана бес жыл кетеді дегенді білдіремін. Шын мәнінде, көптеген компаниялардың жұмыс істеу тәртібіне қарасақ, бес жылды алатын кез келген әзірлеу жобасы ешқашан аяқталмауы әбден мүмкін.
Бұл төтенше жағдай екенін мойындаймын. ITA хакерлері ерекше ақылды сияқты, ал C – өте төмен деңгейлі тіл. Бірақ бәсекелестік нарықта екі немесе үш еселік айырмашылықтың өзі сіздің әрқашан артта қалуыңызға кепілдік беру үшін жеткілікті болар еді.
Бір рецепт
Бұл – үшкір шашты бастық тіпті ойлағысы да келмейтін мүмкіндіктердің бірі. Сондықтан олардың көбі бұл туралы ойламайды да. Себебі, түптеп келгенде, ешкім мұның оның кінәсі екенін дәлелдей алмаса, үшкір шашты бастық өз компаниясының жеңіліске ұшырауына онша алаңдамайды. Ол үшін жеке ең қауіпсіз жоспар – табынның ортасына жақын болу.
Үлкен ұйымдарда бұл тәсілді сипаттау үшін қолданылатын тіркес – «саланың озық тәжірибесі» (industry best practice). Оның мақсаты – үшкір шашты бастықты жауапкершіліктен қорғау: егер ол «саланың озық тәжірибесі» болып табылатын нәрсені таңдаса және компания ұтылса, оны кінәлауға болмайды. Ол таңдаған жоқ, оны сала таңдады.
Бұл термин бастапқыда бухгалтерлік есеп әдістерін және т.б. сипаттау үшін қолданылған деп ойлаймын. Бұл шамамен ешқандай оғаш нәрсе жасамаңыз дегенді білдіреді. Ал бухгалтерлік есепте бұл, бәлкім, жақсы идея шығар. «Озық» және «бухгалтерлік есеп» терминдері бірге жақсы естілмейді. Бірақ бұл критерийді технология туралы шешімдерге көшіргенде, сіз қате жауаптар ала бастайсыз.
Технология көбінесе озық болуы керек. Бағдарламалау тілдерінде, Эранн Гат атап өткендей, «саланың озық тәжірибесі» сізге шын мәнінде ең жақсысын емес, жай ғана орташасын береді. Қандай да бір шешім бағдарламалық жасақтаманы анағұрлым белсенді бәсекелестерден әлдеқайда баяу қарқынмен жасауға мәжбүр етсе, «озық тәжірибе» – бұл қате атау.
Сонымен, бізде өте құнды деп санайтын екі мәлімет бар. Шын мәнінде, мен мұны өз тәжірибемнен білемін. Біріншіден, тілдердің қуаты әртүрлі. Екіншіден, басшылардың көбі мұны әдейі елемейді. Осы екі фактінің өзі ақша табудың нағыз рецепті болып табылады. ITA – бұл рецепттің іс жүзіндегі мысалы. Егер бағдарламалық жасақтама бизнесінде жеңіске жеткіңіз келсе, қолыңыздан келетін ең қиын мәселені қолға алыңыз, қол жеткізуге болатын ең қуатты тілді пайдаланыңыз және бәсекелестеріңіздің үшкір шашты бастықтарының орташа деңгейге қайта оралуын күтіңіз.
Қосымша: Қуат
Бағдарламалау тілдерінің салыстырмалы қуаты туралы нені меңзегенімді көрсету үшін келесі есепті қарастырайық. Біз аккумуляторларды жасайтын функцияны жазғымыз келеді — бұл n санын қабылдайтын және басқа i санын қабылдап, i-ге арттырылған n мәнін қайтаратын функцияны қайтаратын функция.
(Бұл қосу емес, арттыру. Аккумулятор жинақтауы керек.)
Common Lisp тілінде бұл мынадай болар еді: (defun foo (n) (lambda (i) (incf n i))), ал Perl 5-те: sub foo { my ($n) = @_; sub {$n += shift} }, оның құрамында Lisp нұсқасына қарағанда элементтер көбірек, себебі Perl-де параметрлерді қолмен шығарып алу керек.
Smalltalk тілінде бұл код Lisp-ке қарағанда сәл ұзынырақ: foo: n |s| s := n. ^[:i| s := s+i. ], себебі жалпы алғанда лексикалық айнымалылар жұмыс істегенімен, параметрге мән тағайындау мүмкін емес, сондықтан жаңа s айнымалысын жасау қажет болады.
Javascript-те бұл мысал тағы да сәл ұзынырақ, себебі Javascript нұсқаулар (statements) мен өрнектер (expressions) арасындағы айырмашылықты сақтайды, сондықтан мәндерді қайтару үшін анық return нұсқаулары қажет: function foo(n) { return function (i) { return n += i } } (Әділдік үшін айтсақ, Perl де бұл айырмашылықты сақтайды, бірақ оны әдеттегі Perl стилінде шешеді: return-ді жазбай кетуге мүмкіндік береді.)
Егер сіз Lisp/Perl/Smalltalk/Javascript кодын Python-ға аударуға тырыссаңыз, кейбір шектеулерге тап боласыз. Себебі Python лексикалық айнымалыларды толық қолдамайды, сондықтан n мәнін сақтау үшін деректер құрылымын жасау керек. Және Python-да функция деректер түрі болғанымен, оның тікелей литералдық ұсынылуы жоқ (егер оның денесі тек бір ғана өрнектен тұрмаса), сондықтан қайтару үшін атауы бар функция жасау керек. Нәтижесінде сіз мынадай код аласыз: def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar Python қолданушылары неге жай ғана def foo(n): return lambda i: return n += i немесе тіпті def foo(n): lambda i: n += i деп жаза алмаймыз деп орынды сұрауы мүмкін және менің ойымша, олар бір күні солай жаза алатын болады. (Бірақ егер олар Python-ның Lisp-ке айналуының қалған жолын күткісі келмесе, олар әрқашан жай ғана...)
ОББ (нысанға бағытталған бағдарламалау) тілдерінде сіз сыртқы ауқымнан әрбір айнымалының орнын басатын бір әдісі және өрісі бар класты анықтау арқылы тұйықталуды (сыртқы көріну ауқымында анықталған айнымалыларға сілтеме жасайтын функцияны) белгілі бір деңгейде симуляциялай аласыз. Бұл бағдарламашыны лексикалық ауқымды толық қолдайтын тілде компилятор жасайтын код талдауын қолмен жасауға мәжбүр етеді және бірнеше функция бір айнымалыға сілтеме жасаған жағдайда жұмыс істемейді, бірақ осы сияқты қарапайым жағдайларда бұл жеткілікті.
Python сарапшылары бұл мәселені Python-да шешудің ең дұрыс жолы мыналардың бірін жазу екеніне келісетін сияқты: def foo(n): class acc: def __init__(self, s): self.s = s def inc(self, i): self.s += i return self.s return acc(n).inc немесе class foo: def __init__(self, n): self.n = n def __call__(self, i): self.n += i return self.n Мен бұларды қостым, өйткені Python жақтастары мені тілді бұрмалап көрсетті деп айтуын қаламаймын, бірақ екеуі де маған бірінші нұсқадан күрделірек көрінеді. Сіз аккумуляторды сақтау үшін бөлек орын дайындап, дәл сол нәрсені істеп жатырсыз; бұл тізімнің басы емес, жай ғана нысандағы өріс. Ал бұл арнайы, сақталған өріс атауларын, әсіресе __call__ пайдалану біршама «хак» (уақытша амал) сияқты көрінеді.
Perl мен Python арасындағы бәсекелестікте Python хакерлерінің уәжі Python – Perl-дің анағұрлым талғампаз баламасы дегенге саятын сияқты, бірақ бұл жағдай көрсеткендей, нағыз талғампаздық қуатта жатыр: синтаксисі сәл ерсірек болса да, Perl бағдарламасы қарапайымырақ (элементтері азырақ).
Ал басқа тілдер ше? Осы баяндамада айтылған басқа тілдерде — Fortran, C, C++, Java және Visual Basic — бұл мәселені шынымен шешуге бола ма, жоқ па, белгісіз. Кен Андерсон Java-да барынша жақын келетін нәрсе мына код дейді: public interface Inttoint { public int call(int i); } public static Inttoint foo(final int n) { return new Inttoint() { int s = n; public int call(int i) { s = s + i; return s; }}; } Бұл сипаттамаға толық сәйкес келмейді, себебі ол тек бүтін сандар үшін ғана жұмыс істейді. Java хакерлерімен көптеген хат алмасулардан кейін мен алдыңғы мысалдар сияқты әрекет ететін дұрыс полиморфты нұсқаны жазу өте қиын мен мүмкін еместің арасында деп айтар едім. Егер біреу осындайды жазғысы келсе, мен оны көруге өте қызығар едім, бірақ менің жеке уақытым бітті.
Әрине, бұл мәселені басқа тілдерде шеше алмайсыз деген сөз сөзбе-сөз шындық емес. Бұл тілдердің барлығы Тьюринг бойынша баламалы екендігі, қатаң айтқанда, олардың кез келгенінде кез келген бағдарламаны жазуға болатынын білдіреді. Сонда мұны қалай жасар едіңіз? Ең шекті жағдайда — азырақ қуатты тілде Lisp интерпретаторын жазу арқылы.
Бұл әзіл сияқты естіледі, бірақ ол үлкен бағдарламалау жобаларында әртүрлі дәрежеде жиі болатыны соншалық, бұл құбылыстың арнайы атауы бар, Гринспеннің оныншы ережесі:
Кез келген жеткілікті күрделі C немесе Fortran бағдарламасында Common Lisp тілінің жартысының арнайы құрастырылған, ресми сипатталмаған, қатеге толы, баяу іске асырылуы бар.
Егер сіз күрделі мәселені шешуге тырыссаңыз, мәселе жеткілікті қуатты тілді қолданасыз ба, жоқ па дегенде емес, сіз (а) қуатты тілді пайдаланасыз ба, (b) сондай тілге арналған іс жүзіндегі интерпретатор жазасыз ба, әлде (c) өзіңіз сол үшін адам-компиляторға айналасыз ба дегенде. Біз мұның Python мысалында қазірдің өзінде орын ала бастағанын көріп отырмыз, онда біз шын мәнінде компилятор лексикалық айнымалыны жүзеге асыру үшін жасайтын кодты симуляциялап отырмыз.
Бұл тәжірибе тек кең таралған ғана емес, сонымен бірге институционалданған. Мысалы, ОББ әлемінде сіз «үлгілер» (patterns) туралы жиі естисіз. Мен бұл үлгілер кейде жұмыс істеп жатқан адам-компилятордың (c жағдайының) дәлелі емес пе екен деп ойлаймын. Бағдарламаларымнан үлгілерді көргенде, мен мұны қиындықтың белгісі деп санаймын. Бағдарламаның құрылымы тек ол шешуі керек мәселені ғана көрсетуі тиіс. Кодтағы кез келген басқа заңдылық, кем дегенде мен үшін, жеткілікті қуатты емес абстракцияларды қолданып жатқанымның белгісі — көбінесе бұл мен жазуым керек болатын қандай да бір макростың кеңейтілуін қолмен жасап отырғанымды білдіреді.
Ескертпелер
IBM 704 процессоры шамамен тоңазытқыштың көлеміндей болды, бірақ әлдеқайда ауыр еді. Процессор 3150 фунт тартты, ал 4K жедел жады тағы 4000 фунт тартатын бөлек қорапта орналасты. Ең үлкен тұрмыстық тоңазытқыштардың бірі Sub-Zero 690-ның салмағы 656 фунтты құрайды.
Стив Рассел сонымен бірге 1962 жылы алғашқы (сандық) компьютерлік ойын – Spacewar-ды жазды.
Егер сіз үшкір шашты бастықты алдап, Lisp-те бағдарламалық жасақтама жазуға рұқсат алғыңыз келсе, оған бұл XML деп айтып көруге болады.
Міне, басқа Lisp диалектілеріндегі аккумулятор генераторы: Scheme: (define (foo n) (lambda (i) (set! n (+ n i)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _]) Эранн Гаттың JPL-дегі «саланың озық тәжірибесі» туралы мұңды әңгімесі мені осы жиі орынсыз қолданылатын тіркесті талқылауға шабыттандырды.
Питер Норвиг Design Patterns кітабындағы 23 үлгінің 16-сы Lisp-те «көрінбейтін немесе қарапайымырақ» екенін анықтады.
Әртүрлі тілдер туралы сұрақтарыма жауап берген және/немесе осы мәтіннің нобайларын оқыған көптеген адамдарға, соның ішінде Кен Андерсонға, Тревор Блэквеллге, Эранн Гатқа, Дэн Гиффинге, Сара Харлинге, Джереми Хилтонға, Роберт Морриске, Питер Норвигке, Гай Стилге және Антон ван Страатенге алғыс айтамын. Олар айтылған кез келген пікір үшін жауапты емес.
Байланысты:
Көптеген адамдар бұл баяндамаға жауап берді, сондықтан мен олар көтерген мәселелерді қарастыру үшін қосымша бет аштым: Re: Ботаниктердің кегі.
Бұл сондай-ақ LL1 тарату тізімінде кең ауқымды және көбінесе пайдалы пікірталас тудырды. Әсіресе Антон ван Страатеннің семантикалық қысу туралы хатын қараңыз.
LL1-дегі кейбір хаттар мені Қысқалық – қуат мақаласында тілдің қуаты тақырыбына тереңірек үңілуге итермеледі.
Аккумулятор генераторының сынақтамасының канондық іске асыруларының кеңірек жиынтығы өздерінің жеке бетінде жинақталған.