Category: it

Category was added automatically. Read all entries about "it".

Погром в китайской комнате



Вместо введения



Меня удивляет, что у нас принято судить о многом не объективно, а как-то по косвенным признакам. Ну вот, например, картины продают за неслыханные деньги не потому, что они реально хорошо сделаны, а потому, что нарисовал какой-то известный хрен. Или за какую-нибудь кривую муть отваливают в десять раз больше, чем за качественный заводской образец, ибо hand-made. Или творчество там. Почему-то считается, что человек сам обалдеть какой из себя уникальный и незаменимый, и создать что-то новое может только он, а искусственный интеллект — не может и вообще не смог бы, ведь человек-то уникальный и незаменимый, а искусственный интеллект — просто какая-то груда железа.

Но если смотреть по сути, эти вопли, производимые в промышленных масштабах любителями поразвивать свой богатый внутренний мир макдачными эспрессоглотателями, никакой адекватной основы под собой не имеют. В мире здорового человека, если кто-то решил сделать вручную то, что уже изготавливается автоматически, то либо он просто нерационально использует свои ресурсы, либо учится, либо ещё что-то из этой оперы. И платить больше ему никто не станет. Аналогично — с картинами. Если на холсте какая-то мазня, то покупателю до лампочки, что художник был редкий, известный, нарисовал пару картин и уже пару веков как ничего не рисует. На холсте остаётся всё та же мазня, которой грош цена.

Если не мазня, но есть выбор между несколькими копиями — за авторством самого художника, его учеников, поздних искусствоведов и фотографического аппарата, то вовсе не важно, что из этого покупать. Разумеется, если новый владелец решит щупать мазки, вариант с фотокопией отпадает, но все остальные — всё ещё в силе. Я рекомендую покупать версию от искусствоведов. В мире курильщика, в котором нам приходится находиться, она стоит заметно дешевле, но гораздо лучше отражает стиль автора. У самого художника, если это не была не его последняя картина, не было возможности полностью изучить свой стиль. У него был какой-то опыт, но он неполон и уже подзабылся. А если картина последняя, то автор уже слишком стар, чтобы разбираться в своём стиле.

Скажем, он в жизни производил картины пятьдесят лет, и мы покупаем картину с 25го года творчества. У автора в голове только половина знаний о своём стиле, притом первые лет пятнадцать-двадцать уже забылись. Искусствовед же занимался изучением стиля художника за весь период творчества, и это было плотнее по времени. То есть он, скажем, находясь в полном расцвете сил, десять лет изучал творчество художника от начала карьеры до её конца. У него в голове более структурированные знания стиля художника. У него другие приоритеты. Художник хотел есть, пить и гулять и побыстрее сбагрить картину покупателю, чтобы быстрее начать есть, пить и гулять. Он мог где-то полениться и не доделать, где-то отдать дорисовать ученикам. Искусствовед же спокойно сидит на окладе и добросовестно создаёт более качественную копию. Его цель — написать картину как это сделал бы сам художник, если бы не торопился и не хотел так сильно есть, пить и гулять.

Наконец, китайская комната



То же с китайской комнатой. Любители поразмышлять о естественности припоминают её в разговорах об ИИ. Они сравнивают ИИ с комнатой, в которой человек бездумно по словарю подбирает ответ на китайский текст. У такой системы на входе и выходе хороший китайский язык, но человек внутри его не понимает. Этот мысленный эксперимент всего лишь говорит о том, что для успешной коммуникации понимание не требуется, а вещи, которые делают с пониманием, на самом деле с виду мало чем отличаются от тех, которые делают без него.

И, в общем-то, дело закрыто. Всё, можно спокойно расходиться. Наличие понимания от отсутствия понимания ничем не отличается, ИИ эквивалентен человеческому разуму, если производит эквивалентные решение. Ла-ла-ла, песня в том же духе. Но ведь нет, эспрессоглотателям нужно какое-то "понимание" от ИИ. Будто бы если ИИ начнёт понимать, он сделает как-то лучше, душевнее и продуманнее.

И тут, по-хорошему, стоит ответить на один интересный, почти философский, вопрос. Есть ли вообще у нас понимание о том, есть ли вообще у кого-то понимание? Ну то есть справедливы ли все эти всхлипывания про понимание, либо же мы вообще не можем детектировать его наличие.

Чтобы понимать, что вообще мы понимаем, надо понять, как мы понимаем понимание. Мы понимаем понимание как способность выводить из полученных правил и утверждений новые, явно не оговоренные правила и утверждения, и использовать их на практике. Ну то есть вот ещё в школе показывают, что если у Ивана Иваныча было два яблока, а у Ольги Петровны — всегда в два раза больше (она очень завистливая, но имеет богатого мужа, который от этой её черты довольно таки подустал, и потому успокаивает её характер покупкой в двойном размере всего того, до чего соседского смог дотянуться её длинный нос), то у неё фактически их четыре.

Затем спрашивают, сколько бы их было у Ольги Петровны, если бы у Ивана Иваныча было бы два яблока. Здесь все, кто слушал, должны ответить, что таки хотелось бы больше, но имеется только четыре. Это нулевая стадия — передача информации. Такими вопросами можно узнать, имеет ли ученик уровень интеллекта не ниже, чем у куска магнитофонной ленты.

Затем спрашивают, сколько бы их было у Ольги Петровны, если бы Иван Иваныч перестал пить и сходил на базар ещё за одним яблоком. И здесь уже ученику приходится думать. Те, кто остались на уровне развития магнитофонной ленты, обязательно ответят, что четыре — ведь они этот ответ только что слышали. А те, что умеет подставлять формулы — сообщат о наличии шести яблок (уже на килограмм-полтора тянет). Ученик демонстрирует более глубокое понимание и уровень интеллекта не ниже, чем у нескольких команд процессора.

Затем спрашивают, сколько бы их было у Ольги Петровны, если бы она стала ещё более жадной, и муж её стал покупать всего в четыре раза больше, и ещё добавлять сверху две штуки (4x + 2). А затем — о том, сколько бы стало, если бы её мужу это надоело, и он стал покупать в три раза меньше (1 1/3x + 2/3). И это уже новые уровни понимания, на которых у ученика котелок варит, и он уже придумывают свою уникальную формулу под новые задачи. В мире взрослых котируется уже только это понимание, все уровни ниже — суть ололо унтерменшен и поколение ЕГЭ, о котором нельзя упоминать вслух в приличных пивнушках.

То есть, чтобы проверить понимание, у нас есть только критерий "если чёрный ящик сам вывел правило и продемонстрировал его применение, то он понял".

По сути, мы можем проверить уровни понимания только те, которые мы сами можем осознать. А дальше они становятся для нас неразличимы, мы не можем проверить, выводит ли чёрный ящик более тонкие формулы, и считаем, что чёрный ящик имеет глубокое понимание рассматриваемого вопроса. Если чёрный ящик понимает суть вопроса заметно лучше нас, мы либо это не почувствуем, либо вовсе скажем, что чёрный ящик — дурак.

В компьютерных делах



В компьютерных делах используются программы, которые состоят из набора чёрных ящиков — функций (не важно, обычных функций, функций-членов, команд или процедур), которые получают на вход какие-то значения и/или состояния и на выходе производят какие-то значения и/или новое состояние. И дальше понимание программистом программы можно определить как возможность подменить каждую из функций на эквивалентную, если её логика работы будет скрыта.

На первом этапе это соответствие типам данных — размерностям. На входе должны быть пасхальные кролики, на выходе — яйца, и программист должен написать чёрный ящик, который так и делает. Затем, на новом уровне понимания должен быть учтён порядок эффектов. Например, если эта функция сначала писала "получаем новое яйцо...", а затем "зовём кролика", то и копия должна высказываться в том же порядке и звать кролика раньше, чем подготовлена форма под яйцо. Затем в ход идут некорректные случаи. И это уже если не вершина горы понимания программы, то огромный холм, на который уже нельзя въехать на джипе.

Вообще, в некорректных случаях фукция или программа должна работать корректно и максимально адекватно и полезно сообщать о том, что таки что-то пошло не так. В этом плане отличаются программы новичков и опытных программистов, а также программы, написанные за один день и программы, которые писали долго и качественно. Сначала рассматриваются обычные случаи, а затем — исключительные. Новичок может и не дойти до вторых, а опытный сразу их контролирует хотя бы в минимальном объёме. И самое страшное здесь — когда в определённых ситуациях получается некорректное, но удобное для кого-то поведение.

На этом деле тот же Microsoft съел уже не только собаку, но и целую стаю волков с хрустящими после прожарки мощными лапищами. Им пришлось писать новый код так, чтобы поддерживать его результаты и эффекты для набора популярных программ, которые пользовались черными ящиками Microsoft неправильно или же багами в них. Например, какая-нибудь Ололо Кампани Лимитед в своей программе по подсчёту крыс воспользовалась тем, что 00 = 0. В математике за это больно бьют канделябрами в жбан, а в программировании приходится в новой версии функции возведения в степень для случая 00 исправно возвращать 0, а не отправлять пользователя на матерный стектрейс, ведь иначе все будут сидеть на старой версии Windows, где программа подсчёта крыс ещё работает.

Соответственно, эквивалентность замены определяется кодом, который вызывает нашу функцию. Если код простой и корректный, ничего не сломается, даже если программист не понял, что писал в своей версии функции. Если вызывающий код более хитрый и использует скрытые зависимости и нетривиальные эффекты, программисту придётся продемонстрировать понимание получше, чтобы его функция работала с этим кодом.

В криптографии имеется атака по сторонним каналам — когда злоумышленник, имея глубокое понимание работы криптосистемы от математического до физических уровней, может по времени выполнения, уровню энергопотребления, гравитационным волнам от ПК и прочим неважным для математики параметрам расшифровать то, что было надёжно зашифровано. Вообще, все такие атаки и нахождение новых уязвимостей основываются на ультраглубоком понимании сути хакерами.

Что интересно, у психологов в работе используются похожие методы. Они могут посылать испытуемому на вход какой-то бред и судить о корректности ответа, либо же атаковать по сторонним каналам. Например, могут спросить о том, что общего у пингвина и котлеты. Здоровый человек скажет, что они относятся в некотором роде к животному миру, у них есть внутри мясо, и т.п. Другой здоровый человек скажет, что пингвин с головой собаки и котлетка означают неуместность, и тут специалист, который не знаком с культурой Рунета, может заподозрить, что пациент плодоносит скудоумием, и проверить его на других тестах, на которые у пациента не возникнет (с некоторой вероятностью, которая крайне мала) ассоциаций с неизвестными доктору мемами.

А вот нездоровый человек скажет, что пингвин — это четыре лапы/крыла и хвост — пятимерный гиперкуб, а котлета усов, лап, хвоста и прочих документов не имеет, отчего является точкой, а это уже нульмерный гиперкуб, — и в итоге пингвина и котлету объединяет то, что они оба — гиперкубы. Главное — отличить поехавшего от человека, обладающего некоторыми знаниями. Может быть, человек на работе конструирует изоморфизмы, и разум его разработан настолько, что в него помещаются многомерные гиперкубы. Или же если у пациента стоит цель обмануть врача, то он может инсценировать выбранную болезнь, обладая хорошими знаниями используемых в медицине методов.

И вот получается, что уровень понимания — это уровень того, насколько хорошо чёрный ящик моделирует отданную ему на откуп реальность. Но с учётом того, что измеряет уровень понимания не какой-то внешний наблюдатель-Наполеон, а такая же тварь дрожащая, которая, вообще-то тоже не до конца понимает всей сути, сюда добавляется ограничение на уровень понимания наблюдателя. Соответственно, измеренный уровень понимания (максимум, чем мы можем оперировать достоверно, не выходя из научных рамок) — это уровень того, насколько хорошо чёрный ящик проходит тесты наблюдателя, который считает, что понимает реальность. И этот уровень ограничен уровнем наблюдателя.

Заметьте, я не говорю "не выше", а использую термин "ограничен". Это важно. Не всегда нужно понимание вопроса не ниже уровня чёрного ящика, чтобы оценить понимание этим ящиком вопроса. Например, тесты вида "выбери один вариант из четырёх" с фиксированным набором вопросов и подсчётом баллов могут адекватно оценить уровень понимания, хотя сами по себе знают только фиксированный набор фактов и не обладают возможностью мыслить. С другой стороны, можно узнать правильные ответы и сколько угодно обманывать такой тест — где-то тут начинают прорисовываться упомянутые ограничения.

Карлсон не лучше собаки



Итак, по факту, мы можем получать знания об уровне понимания чего-то кем-то другим только через субъективные тесты, с ограниченной точностью и с ограниченным диапазоном измерений. Где-то в глубине души (кстати, где она?) у нас лежат идеи о каком-то волшебном самосознании и не менее волшебном глубоком и неповторимом понимании, а также способностью творить, которыми обладают якобы только биологические нейросети. Хотя вот искусственная нейросеть уже умеет придумывать формулу по отдельным исходным данным, и это уже больший уровень понимания, чем у "поколения ЕГЭ", которое "только и умеет, что подставлять готовые формулы".

Нужно ли вообще это самосознание на практике — это философский вопрос. И ещё риторический, потому что отвечать на него я, конечно, не буду. На практике надо ещё измерить, что в итоге выгоднее — европейский подход с личными ценностями, либо азиатский — с трудом на благо общества; автомат, который не устаёт и доставляет одинаково высокий уровень качества, единственная цель в жизни которого — выполнять свою работу хорошо, или человек со своими эмоциями, который сегодня в настроении, завтра его пофилософствовать потянуло, послезавтра надо пораньше уйти и оставить клиентов у закрытой двери. Не знаю, как там насчёт самосознания у людей и на чём построена их мотивация, но у автоматики без всякой такой мути как самосознание работа получится качественней.

Я уже сейчас могу сказать, что поиск Google большую часть времени понимает меня гораздо лучше, чем люди. Часто он поразительно метко дополняет мои запросы, иногда подсказывает даже то, о чём я только начинал думать. И при этом я сомневаюсь, что у него есть какое-то важное для эспрессоглотателей самосознание и прочая чепуха. Поиск просто работает, не уходит вечером пораньше, не уходит в отпуск, не выдаёт какую-то чушь из-зи плохого настроения и не говорит, что потратил на меня свои лучшие годы. А если поиск работает лучше, то чем он хуже человека?

Не говоря уже о том, что поиск Google гораздо интеллектуальнее какого-нибудь бурчащего "здрасте, какпагода, дасвиданья" коллеги. Что, любители глубокой души и интеллекта решили возразить? Я уже слышу, как говорят, что бурчащий коллега имеет три образования и IQ over9000. А помните, какой у нас единственный способ определить уровень понимания? А какой уровень понимания получит простой скрипт "здрасте, какпагода, дасвиданья"? И чем вообше различаются в вашем взаимодействии этот простой скрипт и бурчащий коллега, если вы в общении не заходите дальше "здрасте, какпагода, дасвиданья"?

Если ценители глубокой души ещё не въехали, вот ещё пример. Допустим, есть жестокий убийца. Всех встретившихся сначала материт и забивает канделябрами, потом расчленяет, мажется кровью и громко хохочет. Известно, что внутри, где-то очень глубоко, он добрый-предобрый, всех своих жертв очень сильно любит, и вообще и мухи не обидит. И вот эта душевная доброта у него ну вообще никак не проявляется. Всё, что видят люди — догнивающие расчленённые трупы и вжухи канделябров перед глазами. Будете вспоминать эту внутреннюю любовь и доброту, когда на вас пойдут с канделябром и крепким словцом? Ведь нет же, не будете.

Вот и я говорю, что весь этот ваш богатый внутренний мир нахрен никому не сдался. Всё, что мы про этот идиотский внутренний мир знаем — его косвенные проявления на словах и в поступках. Ну и вот результаты поиска Google объективно говорят о его начитанности и богатом внутреннем мире. А коллега-бурчалка кроме "здрасте, какпагода, дасвиданья" ничего не сказал, и потому он объективно ничем не отличается от бездушного неотёсанного чурбана без богатого внутреннего мира.

Всё, что вам нравится — это шум



Весь тот бред про индивидуальность, изюминку, загадочность души можно заменить простым генератором случайных чисел. Это не мастер слова родил новый литературный шедевр, это генератор случайных чисел запустил шестерёнки шизофазии. Это не индивидуальность и вдохновение и его отсутствие, это генератор случайных чисел выдаёт разные коэффициенты меньше единицы, на которые домножается производительность труда. Это не проявление живых эмоций, это генератор случайных чисел выдал команду отреагировать так или сяк.

Любители грампластинок любят тёплый ламповый скрежет иглы. Любители старых фотографий умиляются зерну — банальному шуму на изображении. Любители объективов охотятся за старыми советскими экземплярами. Тогда не было такой повторяемости и высоких технологий как сейчас, можно было запороть несколько штук, после чего у них появлялся свой "характер". Неспособность работать против Солнца (плохое просветление оптики) воспринимается уже не как недостаток объективов, а возможности для творчества. А современные объективы ругают за сухость картинки.

Идеальность лиц, воспроизведённых компьютерной графикой, воспринимается как что-то жуткое. Или там ругаются на фотографов, которые перебарщивают с обработкой. А вот если добавить идеальнму лицу асимметрию, всяких там родинок и веснушек в случайных местах, волосы немного торчащие сделать, нос чуть кривой забацать — тогда получится человек, что надо. К таким мозги привыкли, таких людей мы знаем и любим.

Порядок неестественен. Порядок не несёт энтропии — не несёт информации — скучен и хорошо сжимается архиматором. Шум же и беспорядок наоборот всегда радует и удивляет. А творческий беспорядок ценится как важная черта человека искусства. И вот получается, что чтобы удовлетворить любителей уникальности человека и его глубокой души, необходимо сначала создать робота, который сделает всё как надо, а потом испортить его.

Вставить случайные коэффициенты уменьшения производительности и смены настроения, добавить возможность отвлекаться и болтать ни о чём, натянуть опухшую после вчерашнего и чуть печальную резиновую мордаху. И тогда любой экстраверт и макдачная илитка скажет, что вот он, вот он человек его мечты, внутренний мир здоровенный, здоровенный ВНУТРЕННИЙ МИРРР. Поговорит с ним, поэмоционирует и обязательно заявит, инженеру в замасленном халате, что вот он — человек, человек с большой буквы, человек, который его понимает, имеет такой глубокий взгляд, и никакая автоматика этого не заменит. А инженер просто стянет у робота резиновую мордаху и посмотрит на ценителя глубоких душ с ухмылкой.

И вообще, вся эта душевность объекта связана вовсе не с его характеристиками, а с характеристиками субъекта. Если субъекту показать объект, который внешне не похож на человека, тот ему не будет сочувствовать и объявит бездушной машиной. А стоит показать антропоморфного робота или милую зверушку из мультфильма, то у субъекта сразу включится эмпатия, а душевность объекта внезапно повысится. Аналогично — с состоянием субъекта. Если субъект трезв, мир вокруг жесток, и субъекта никто не понимает. А стоит субъекту бахнуть по маленькой, так сразу просто магическим образом найдутся те, кто его понимает. Это будет и ранее бывший бездушным друг, и собака. И даже неживые предметы, если как-то так оно вышло не по маленькой бахнуть, а сразу пузырь выдуть.

Квантование человека и, собственно, разгром китайской комнаты



И вот мы таки подобрались к самой сути, до которой так медленно ползли. Стоит ли весь бред, связанный с китайской комнатой, воспринимать всерьёз?

Итак, обычного китайца заменили комнатой, где сидит непонимающий человек со словарём, и никто не догадался. А что вообще будет, если в комнату посадить не человека со словарём, а двух человек? Один из них будет знать китайский, а другой — проксировать к нему запросы? То есть отвечать будет снова непонимающий, но инструментом перевода будет не бездушный словарь, а понимающий человек. Снаружи снова не заметят разницы, но конфигурация комнаты будет несколько другой.

Заметьте, снаружи любой сможет убедиться, что комната в целом понимает китайский, не важно, внутри китаец, не-китаец со словарём или не-китаец, проксирующий китайца. И, в принципе, всё это эквивалентно одному китайцу, отличается только наше разбиение этого реального/виртуального китайца на составляющие.

Когда мы говорим о понимании кем-то чего-то, мы рассматриваем человека как неделимое существо, которое понимает. А что если мы логически разделим человека и посмотрим на результаты? Допустим, на мозг и систему ввода-вывода. Это прямо таки эквивалентно моей модификации китайской комнаты. Мозг понимает, а рот и уши разговаривают безо всякого понимания.

А если разделить на более мелкие части, можно прийти и к варианту, что в сети нейронов закодирован словарь, к которому остальные системы находят доступ для ответа и бездумно используя бездушную бионейросеть выдают результат. Или вовсе можно разделить человека на нейроны, клетки или молекулы, и тут вдруг окажется, что по-отдельности эти части ничегошеньки не понимают, а цельный человек — вот так вдруг внезапно всё понял и мыслит. Чудеса, да и только.

Соответственно, человек как таковой полностью эквивалентен китайской комнате в изначальной постановке задачи, просто мы не делим его ни физически, ни логически на части, иначе бы получили тот же парадокс. Рассматривать одну сложную систему (человека) как целое, а другую (китайскую комнату) — как набор частей — просто некорректно. Ту систему, которая уже начинает понимать, можно назвать разумной, и состоять она будет из неразумных частей.

Кстати, так же как это неделение приводит нас к ошибкам мышления, из-за которых мы ценим "внутренний мир", "понимание" и "душевность", так и оно же заставляет нас быть уверенными в том, что искусственные нейросети могут решить наши проблемы. Искусственные нейросети — тоже сложная система, о которой, как правило, можно, как и о человеке, судить только если рассматривать её практически целиком. Из-за этого рождаются разные заблуждения. Если мы массово научимся разбираться с нейросетями на всех уровнях — от сети в целом до отдельных нейронов — как при обучении — так и при трактовке результатов, исследования в области ИИ резко продвинутся.

А искусственные нейросети действительно решат наши проблемы. Но чуть позже. Будут у нас, слава роботам, и понимающие друзья, и работники, которые не будут уходить пораньше.

P.S. Я вот не сумел в теги, и теперь автоматика на Flickr и @lj_frank_bot в ЖЖ так хорошо и душевно категоризируют контент.

Юра, мы всё портим. Мясные игры

Прости, Юра. Мы всё про#6@ли.

Прости, Юра. Мы всё портили, портим и будем портить. Это наша суть. Если и рождается в мире светлая идея, то она обречена на погружение во мрак. Если и есть что-то хорошее, то оно неминуемо превратится в страшного демона, который летает по ночам и губит невинные души.

Как только светлая идея появилась в чьей-нибудь голове, около неё сразу создаётся очередь тех, кто с особой усердностью готов её извратить самым ужасным способом, до которого только может дойти человеческий разум. Только лишь идея изливается в мир, как от неё уже во всю несёт гнилью. И с каждым годом по мере продвижения очереди запах становится всё более нестерпимым, пока от изначально светлой идеи всех не начинает тошнить, и её с позором предают забвению.

Хачу оргументов, - скажет привередливый читатель, готовый уже извратить и светлую идею данного текста, если ему дать на то хотя бы небольшой шанс. А сам не замечаешь, по сторонам не смотришь, ленишься? Ну ладно, так и быть, расскажу.

Одна из самых извращённых идей - идея быстрого перемещения в пространстве. Здесь уже сложно что-то испортить. Идея авиаперелётов искажена чуть менее, чем полностью. Казалось бы, прекрасная возможность быстро преодолеть расстояние. Но нет, сначала надо чёрт знает сколько ехать до аэропорта. Ну ладно, а потом лететь? Нет, потом чёрт знает сколько времени возиться с ненужными формальностями. Параллельно приходится стоять в трёх очередях и некоторое время сидеть в двух-трёх местах. В этих жестоких условиях у тебя забирают воду и заставляют покупать еду в два раза дороже, чем та стоит даже в нескромном ресторане. И всё это за билет, на который надо с зарплаты откладывать целый месяц, ничем при этом не питаясь.
Как должно быть на самом деле? Приехал в аэропорт, показал билет, отдал сумку мужику у багажной двери, прошёл в самолёт, ожидаешь отправления. Ровно как в автобусе.
Но как иначе маркетологам продавать нам воду по 10 евро за литр?

Что исчо? Банковские переводы. Сколько стоит достать из базы пару чисел, отправить их по сети и положить в базу ещё немного чисел? Одна копейка? Десятая часть копейки? Если выкрутить в каждом отделении банка одну лампочку, на сколько транзакций хватит энергии? Можно чуть более, чем бесплатно гнать по миру миллионы денег, причём перегнать один миллиард евро стоит столько же, сколько и одну копейку. На сумму отводится только несколько байт в пакете, значительной разницы в длине нет. Всё больше людей пользуются электронными карточками. Деньги в большинстве случаев не надо возить ни людям, ни банкам. Но что мы видим? Перевод из банка в банк - за комиссию, из страны в страну - за большую комиссию и пять рабочих дней (стоит отметить, что даже с учётом медленных проводов, где информация гуляет дольше, чем свет в вакууме, поднятия БД из кэша на жёстких дисках, нескольких посылок пакетов для хендшейка, поиска данных в БД, подтверждений транзакции, время транзакции всё равно исчисляется секундами), да даже за перевод в другой город в одном банке надо платить, обмен валют - аналогично. Ещё и за возможность освободить банк от перевоза мешков денег и путём пользования карточкой тоже надо платить. Не стоит уже говорить, что мы за тысячи лет развили науку, побывали в космосе, но так и не договориться о единой мировой валюте. Саму светлую идею универсальной меры материальных вещей извратили до обменов валют и комиссий.

А как насчёт товаров и услуг? Технологии развиваются. Материалы становятся прочнее и дешевле. Возможности - больше. Компьютеры - быстрее, алгоритмы - эффективнее. Аккумуляторы - более ёмкими.
Но товары, которые нам предлагают, стоят столько же, умеют столько же, но зато становятся более хрупкими. Идею технологического прогресса мы тоже извратили.
Но как? Материалы - дешевле и надёжнее, технологии - продвинутей, но вместо прогресса - регресс. Те же телефоны, чёрт бы их побрал. Съёмный аккумулятор просрали, массу просрали, производительность просрали, прочность просрали. Как выглядит телефон? Скучный прямоугольник, похожий на лезвие. Его сложно купить, легко погнуть и невозможно не поцарапать. На лезвие надевают чехол и чехол для чехла, носят внешний аккумулятор. Но где те сраные лёгкие и маленькие телефоны, про которые нам болтают в рекламе? Если со стограммовым телефоном нужно носить чехол 50г и аккумулятор 200г, без которых жить становится мучительно больно, то нельзя ли сразу выпустить телефон массою 300г? (Не, не 350, а 300, я умею в математику) Открою секрет, что он как монолит будет прочнее и легче, чем безумные наслоения чехлов.
Как быть с теми, кому хватает лезвия без чехлов и обвесов? М - модельный ряд. Пусть уже телефоны отличаются чем-то ощутимым, а не только ценою. Офисный житель Вася купит лёгкое лезвие с беспроводной зарядкой, которым легко крутить на скучных митингах, а фотограф губ уточкой Маша купит кирпичик, заряда которого хватит на недельный уход из реальности в бутики.

Кстати да, идею информационного общества и мировых коллекций знаний мы тоже извратили до передачи фотографий губ уточкой, сплетней и фейков. И да, даже в тех скудных уголках общественного сознания и всемирной сети, где не умерла ещё идея мировой коллекции знаний, мы наблюдаем идею наживы и возни копирастов. Всё, что нужно научному журналу - исправить пару опечаток в статье, написанной и проверенной достаточно образованными людьми. Её уже в таком виде приносят им на тарелочке с золотой каёмочкой. Но нет, этого им мало. Надо, чтобы ещё кто-нибудь заплатил - либо сами авторы, либо читатели, либо все вместе, а лучше - чтоб и те, и другие, и ещё не по одному разу. Светлая идея мирового просвещения, роста и развития извращена и распята на деревянном кресте при наличии лёгких карбоновых аналогов.

Кстати, есть ещё библиотеки, где выдаётся ограниченное количество электронных книг, будто бы это бумажные книги, ради которых надо вырубать деревья и тратить чернила на печать. Вообще, метаидея придания качеств устаревшего говна новым технологиям - один из самых распространённых способов извращения светлых идей. Это и поштучная продажа/выдача электронных версий чего-либо, и комиссии за перевод денег, когда банкноты физически никуда не едут, и бюрократизация ПО для бюрократов. Ради дженуинного экспириенса в фильмах и играх замыливают, затемняют или расшатывают картинку как оно было бы в "реальной жизни" (реальной жизни с космолётами и драконами), даже если об этом их никто не просил.

А как же программируемые устройства? Ведь с появлением программирования, логических устройств общего назначения мы должны были достичь невиданных горизонтов? Произвести по отлаженному процессу триллионы однотипной чепухи, которую можно запрограммировать на любые операции? Универсальность, взаимозаменяемость, всеобщее процветание и братство. Нет. Традиционный индейский фигвам. Играть в взаимозаменяемость, программируемость и процветание может только производитель. Он пользуется всеми благами универсальности и простой кастомизации, а на выходе изрыгает жёстко зафиксированный продукт. Это и ПО, которое юридически нельзя дописать/пофиксить, и программные блокировки функциональности. Они продают одну и ту же модель, в разные инстансы которой просто-напросто зашиты разные настройки - объявляется целый модельный ряд, одна и та же вещь продаётся по целому спектру цен. И всё только потому, что в одном файле исправили пару-тройку строк. Оказывается, что прошивка топовой версии стоит 2000 долларов, прошивка средней - 500 долларов, прошивка бюджетной версии практически тем же файлом - пара центов. Как ещё объяснить разницу в цене, если остальные компоненты - те же самые, и файл поправить - много ума не надо.

А что же в светлом будущем, где будут кошкодевочки и искусственный интеллект? Кто не хотел бы заменить своё хмурое примитивное мясное окружение на совершенный, приветливый и неустающий искусственный разум? Особенно, если стоимость реализации будет такая, что каждый сможет позволить обзавестись такой весёлой компаний за сущие копейки? Со стороны государств придут новые налоги и пачки невыполнимых стандартов, пошлины в фонд угнетённого мяса. Ретрограды будут регулярно ходить на акции протеста, утверждая о связи кошкодевочек с ростом преступности и крича об отупении тех, кто общается с искусственным интеллектом (на минуточку - с высшим разумом). Производители введут абонентскую плату, которую будут оправдывать "бесплатными" принудительными техосмотрами и зависаниями на время ежедневных обновлений, причём без этих операций кошкодевочки будут начинать царапаться. По указке активистов или любителей дженуинного экспириенса их наделят чертами устаревших аналогов. Кошкодевочек придётся не только заряжать, но кормить и разговаривать с ними. С новыми обновлениями будут приходить установки от маркетологов. Кошкодевочки будут желать ровно ту дорогую и бесполезную чепуху, коей на полках больше всего осталось нераспроданной (какое совпадение). С наличием бэкапов и огромных хранилищ будущего нельзя будет забэкапить своих искусственных друзей. Придётся каждый раз начинать общение с ними сначала. С возможностью кастомизации искусственного интеллекта в гигантских пределах покупателю будут всучивать сварливый искусственный интеллект, за хороший характер, прописывающийся парой строк в конфигурации придётся платить в 10 раз больше и оформлять особую подписку. При этом всём набигут моралфаги и протащат законы о свободе мысли ИИ, приравняют к убийству отключение хост-машины, на которой крутится ИИ, запретят под флагом защиты индивидуальности ИИ копирование успешных конфигураций, а может и даже бэкапы, из-за чего "защищаемый" ими ИИ будет реально умирать при поломке хост-машины. Но при всей свободе мысли ИИ с обновлениями будут приходить новые идеологические установки. Искусственные друзья с обновлённой промывкой (тьфу, прошивкой) будут рекомендовать купить в кредит машину, чтобы повысить свою солидность для других членов общества, лечиться новыми гомеопатическими препаратами, выступить за свободу мысли ИИ. И несмотря на обучаемость ИИ, эти идеи будут вшиваться в него без возможности переубедить извне.

В итоге мы, всё извратив, продолжим свои мясные игры в отсталом кругу сапиенсовых хомов. Достижения науки и техники, реально стоящие жалкие копейки, смогут позволить себе использовать только богачи. Не будем мы массово покупать пачки экзоскелетов и кошкодевочек, не будем по вечерам беседовать с ИИ и учиться. Мы будем стоять в очереди на телепорт и кормить монетками стационарные бинокли на набережных, которые будут обычными отлитыми в металл VR хедсетами, рендерящими 3D модель по публично доступным картам. Юра, мы всё испортим.

Десятибуквенные домены второго уровня будут стоить всего 1000 долларов в месяц. Один IPv6-адрес - 1000 рублей. Налетай, подорожало!

Нибировчане, когда будете перезаряжать планету более интеллектуальной жизнью?

Компьютерная грамотность

Тонкий интерфейс


Уважаю Apple за минимизацию ненужных прослоек. Ещё в прошлом веке было понятно, что компьютерная грамотность и какие-то особые знания для работы с ними - это лишь следствие несовершенства дизайна интерфейсов. Человек не должен знать что либо, кроме своих профессиональных секретов, чтобы компьютеризировать свою работу.


Реклама Apple Macintosh Portable, 1989

Ещё ссылки на 2 копии, а то видео, вставленное в оригинальный пост, удалили:


Просто прошло не так много лет со дня создания персонального компьютера, его интерфейс ещё не успел отшлифоваться. Раньше не хватало мощностей для работы нормального графического интерфейса. Ресурсов команд разработчиков не хватало для работы над дизайном и взаимодействием с пользователем.

Теперь мы стоим на плечах гигантов, придумавших библиотеки, тестирование, языки высокого уровня, сборку мусора, сбор информации о взаимодействии. Теперь уже написано большинство нужных нам библиотек, существуют нетормозящие абстракции любого уровня.

Эволюция от выживания к жизни


Раньше разработчику приходилось думать на уровне инструкции процессора, потом - на уровне массива данных, теперь - на уровне абстрактного типа данных. Всё меньше он привязан к компьютеру и всё больше - к терминам предметной области. А чем ближе к абстракциям предметной области - тем меньше ресурсов нужно для реализации функционала.

Крупные компании без ущерба для себя и нас теперь имеют техническую возможность собирать статистику и анализировать свои интерфейсы, имея данные о миллиардах кликов! И если они этим пользуются, то могут чуть подвинуть кнопки в новой версии, чтобы мышь проходила меньшее расстояние; увеличить часто используемые элементы, объединить частые последовательности действий...

С определённого момента, когда все основные программы уже написаны, незадействованные мощности IT-специалистов можно пускать на улучшение уже имеющегося ПО. И чем дальше мы продвигаемся по временной оси, тем больше сил будет направленно именно на улучшение взаимодействия с ПК.

Что нового появилось в старых версиях компьютера (будем рассматривать программно-аппаратный комплекс)? Возможность хранить файлы! Командная строка как у хакеров вместо ввода с перфокарт! Цветной монитор! Доступ в Интернет! Проигрывание аудио без тормозов! Проигрывание видео без тормозов! Игры!

На качественном уровне просто целая революция произошла. Ещё вчера фича просто не могла выжить, а сегодня вдруг появилась.

Что нового появилось в новых версиях компьютера? Более быстрый доступ к файлам. Более быстрый доступ в Интернет. Хранение файлов в Интернете. Проигрывание аудио из Интернета. Проигрывание видео из Интернета. Более быстрый доступ в Интернет. Поддержка более нового стандарта беспроводной связи, проводной связи, шины данных.

Технологично, но негусто. Да, сейчас можно постоянно носить с собой портативный ПК, встроенный в телефон. Да, новые стандарты и игры. Но на качественном уровне компьютер делает то же самое, только с каждым годом лучше. То есть в некоторых областях мы уже дошли до той точки, когда всё работает, и можно это дело только оптимизировать и эффективнее автоматизировать. Фичи, которые раньше просто выживали, начинают жить и развиваться.

Вот увидите, скоро не станет "компьютерщиков" и "тыжпрограммистов", успейте найти мужа сейчас, пока предлог "у меня компьютер глючит" ещё работает которые помогали простым пользователям с их техникой. Простые пользователи будут ещё меньше знать об ИТ, но взаимодействовать с ПК будут эффективнее.

Мануалы не нужны


И если сейчас для некоторых категорий пользователей работает схема "прочитать трёхстраничный текстовый мануал по компиляции и установке программы, провести недельку в установке методом проб и ошибок, прочитать десятистраничный текстовый мануал, чтобы понять программу", то в будущем это будет абсолютно недопустимо. Можно будет сказать автоматизированному помощнику "я хочу установить %programname%", и он проведёт установку самостоятельно, а при пользовании программой - наводить мышь на элемент управления для получения справки. В будущем и вовсе можно будет сказать "я хочу сделать %действие%", и автопомощник скачает программу с наиболее удобным с точки зрения опыта пользователя интерфейсом, изучит мануалы к ней, и в дальнейшем его можно будет просто спрашивать.

Уже сейчас гугление ответов на stackoverflow популярнее инструкций к библиотекам. Почему? Потому, что исключается лишняя абстракция.

Изучение библиотек не нужно


Как это работает?
  1. Имеется пространство задач (то, что должна уметь библиотека)
  2. Программист A находит базис X в пространстве задач и реализует его (создаёт модель задачи, проектирует программные интерфейсы
  3. Программист A описывает базис X словами (создаёт документацию по каждому классу/методу Xi
  4. У другого программиста B появляется задача Y = &Sigma ai Xi, причём ai неизвестны.
  5. Программист B подмечает, что библиотека X ему поможет (видно, что задачу как-то можно решить с помощью библиотеки X, т.к. она делает что-то подобное)
  6. Программист B изучает все Xi и находит координаты Y в базисе X (изучает документацию, понимает философию библиотеки и пишет программу)


Слабые места в этом алгоритме –
  1. Необходимость изучения посторонней библиотеки при имеющихся знаниях синтаксиса языка, принципов работы того, на чём построена библиотека.
  2. Неспособность понять философии библиотеки и эффективно её использовать. Любой человек ограничен умом. Программист A с большой вероятностью может сделать неоптимальную библиотеку с неэффективным интерфейсом, программист B с большой вероятностью может не осознать всех принципов и взаимосвязей.


Мой любимый пример: для получения элемента списка нужно линейное время, для получения всех элементов списка нужно линейное время. Но если не нарушать абстракцию и пользоваться for ... get(i), выйдет квадратичное время. В этом заключается слабость реальных абстракций. Пока ты не понял, что внутри, у тебя могут возникнуть проблемы с их использованием. Но если тебе пришлось заглянуть внутрь, то это какая-то неполноценная абстракция.

Переформулировка задачи не нужна


В случае с чужой библиотекой мы имеем следующие преобразования информации: формулировка задачи из мысли заказчика, формулировка задачи в терминах библиотеки, реализация в терминах языка программирования. По идее, формулировку задачи в терминах библиотеки можно и нужно исключить или хотя бы минимизировать. В идеале, библиотека создана именно для того, чтобы не писать уже существующий код заново. Изучая библиотеку, программист получает ещё и лишнюю точку отказа, появляется новая возможность добавить баг или снизить производительность.

Часто программист, когда сталкивается с чем-то новым, вообще не знает, что ему нужно. Он идёт как бы напролом и неправильно группирует свои действия. Как программист, он разделяет свою задачу Y на Y1 и Y2, т.е. находит некоторый свой базис исходя из имеющегося опыта (который не работает в случае в новой библиотекой X) и спрашивает на форуме. Ему задачу раскладывают по басису X и отвечают:
Y1 = a X1 + b X2 + c X3
Y2 = -a X1 - b X2 + d X3
Оказывается, что проблема решается стандартными средствами, и решить её как целое легче, чем неправильно разложить на подзадачи и решать уже их. Меньше разложений по базису и смен координат.

К сожалению, сообщество стимулирует бездумное поведение и имитацию бурной деятельности. Приходит человек на форум, спрашивает, как сделать Y. Ему говорят, что это элементарно, и надо просто погуглить. Или что вопрос слишком общий, нужен поконкретнее. Он гуглит, ничего не находит, разделяет задачу на подзадачи, спрашивает про подзадачи, ему отвечают по каждой в подробностях, потом интересуются, зачем это. Человек говорит "для Y" и ему тут же сообщают "Да так бы и сказал, это (c+d) X3!" В дальнейшем человек приспосабливается и задаёт совершенно бесполезные конкретные "умные" вопросы.

И вопрос "я хочу реализовать Y" превращается в "я хочу понять X". Заметим, что задача из описания заказчика уже перетекла в какое-то её понимание руководителем отдела разработчиков, сформулирована в письменном виде, как-то понята и смоделирована конкретным программистом, который уже готов исказить этот поток информации ещё несколько раз, выразив своё понимание на языке программирования, который он знает как свой родной язык, либо перевести свои идеи с языка программирования в метаязык библиотеки.

Аналогично с примером с форумами, здесь архитектор может сделать неудачную модель задачи заказчика (наклонить базис куда попало) а программист – неправильно выразить её в терминах ЯП (создать ещё одну систему координат под другим углом). В итоге решение задачи будет гораздо более сложным, чем если бы заказчик сам написал программу.

Это ненужное искажение информации, ненужные затраты по изучению чёрт знает чего.
Гораздо удобнее, когда знаток библиотеки X сам разложит твою задачу по её базису. Он сделает это по определению оптимальнее тебя, т.к. он уже осознал X, а также не будет совершать лишних действий по изучению X, т.к. он уже осознал X. Это банальная окупаемость. Если мне нужно один раз доехать к другу в гости, я вызываю такси за 500 рублей. Если мне нужно ездить к нему раз в день в течение двух лет, я покупаю велосипед за 10000 рублей.

Кроме наличия экспертов, stackoverflow предоставляет нам базу реальных примеров. Автору библиотеки придумывать их - нестерпимая мука и занятие бесполезное. А вот сообщество может сформировать её довольно легко исходя из собственных потребностей!

Языки программирования не нужны


Если не рассматривать программирование как творческий процесс, в котором программист самовыражается, выпуская сборники стихов на языке программирования, а вернуться к исходной задачи, программирование - это вспомогательная дисциплина, направленная на автоматизацию реальных задач. А потому, язык программирования – просто ещё один лишний интерфейс, который мы по историческим причинам должны использовать.

Заказчику нужно автоматизировать некоторую задачу. Ему не нужно, чтобы кто-то изучал библиотеку X и ЯП Z (это дорого и не всегда окупается), ему нужно автоматизировать свою задачу. Лишние прослойки вроде программиста, языков программирования, компьютеров и даже программ не нужны. Если бы заказчик мог купить решение задачи, он бы его уже купил и радовался, а не звал бы кого-то.

Проще метаинтерфейс – проще интерфейс


С течением времени программирование упростилось, компьютеры развились, и разработчикам стало легче создавать программы, осталось больше времени на решение задачи. Чем проще интерфейс у программиста, тем легче ему создать простой интерфейс для пользователя.

Изначально каждый новый язык (или программа) стремится быть простым и понятным всем. Но потом оказывается, что просто он может описывать только ограниченный набор задач, что имеются некоторые абстракции, которые надо знать изнутри. И вот либо язык начинает включать в себя сотни новых фич (по сути - аналогия редких слов и терминов в естественном языке), появляется класс знатоков этого языка (языковеды, исследовавшие все тонкости), простой интерфейс не выдерживает реалий.

Тут ещё нужно вспомнить стремление человека к усложнению. Если людям дать волю, они придумают этикет, сарказм и годовые отчёты, чтобы заменить свободное общение на неповоротливую громоздкую машину намёков, домыслов и спецтерминов. Как минимум, разделение на "ты" и "вы" (имеющее более двух градаций в некоторых языках!) – только оверхед, который не обеспечивает никакой конкретики, вызывает обиды и засоряет память носителя языка.

К счастью, человек ленив, а потому стремится спихнуть свою работу кому-то другому. Сейчас поисковики находят конкретные существующие примеры кода, то есть, можно сказать, преобразуют словесное описание элементарных действий в код. В будущем они научатся преобразовывать более общие описания в код (станут искусственными программистами), и программист будет заниматься только отдельными частными моментами и, возможно, оптимизацией. Компиляторы и сейчас неплохо работают, но в будущем их объединят с искусственными программистами. Зная задачу целиком, компилятор сможет эффективно оптимизировать её. Поначалу программистами будут те, кто сможет сформулировать задачу, а затем, развив модули распознавания, программистов вычеркнут из цепочки.

Модульность для ограниченных или философия компиляции


Почему мы используем модульность? Потому, что это универсально, удобно? Потому, что можно заменить один модуль и не трогать всю систему?

Нет. Мы используем модульность потому, что мы ограничены в ресурсах. Если бы можно было иметь и обслуживать пожарные шланги до каждого дома, их бы сделали вместо модульной фигни. Если бы можно было понять программную систему от начала и до конца, от общих принципов до мельчайших подробностей, то модульности не было бы.

Мы не понимаем свои программы целиком, мы не можем выплавить себе новую вещь, поэтому пользуемся универсальными модульными решениями. И они почти всегда менее хорошо, чем "цельнокройные".

Отличный подход реализуют компиляторы. Мы создаём удобный для понимания модульный код (модулем может быть функция, тип данных, пакет), реализующий полезный функционал (то, что нужно) и взаимодействие модулей (чистый оверхед), а компилятор пробует смотреть на это как на единую программу и совершает эквивалентные преобразования, избавляясь от бесполезного/повторяющегося кода, который неизбежно вносится в любую модульную систему (т.к. поведение реального модуля может отличаться от тех рамок, в которых его держит его интерфейс и требуется как минимум поддерживать корректное состояние объекта в любом случае, даже если в реальности его методы могут вызывать в строго определённом порядке и не требовать корректности между этими вызовами).

По сути, схема работает на любом уровне. Заказчик как-то формулирует задачу на своём языке, приглашает подчинённых (биомодули), которые знают отдельные подзадачи, команда разработчиков её как компилятор полностью осознаёт и пишет модульную программу (хотя менеджер проекта может хотя бы попытаться видеть систему целиком), которая более оптимальна, если бы задачу рассматривали как набор отдельных взаимодействующих подзадач от биомодулей заказчика, затем её полностью осознаёт компилятор и делает более оптимальной код исходя из знаний программы целиком/почти целиком.

Налицо схема преобразования задачи: от сырой общей сути (мысли заказчика, мысли разработчика) в некоторое модульное описание (ТЗ, модули программы) и от модульного описания к конкретному продукту. Чтобы модульность и абстрактность работала в полную силу и не тормозила, её нужно сломать и рассмотреть целиком сверху донизу! Модульность даёт удобное для человека промежуточное представление эффективной монолитности.

То есть чем меньше модулей, тем при возможности реализации/осознании единой сущностью более эффективна концепция.

Люди не смогли сразу выполнить свои задачи, поэтому разделили их реализацию:
  1. Пользователь формально описывает задачу
  2. Разработчик создаёт программу
  3. Пользователь общается с программой
  4. Программа выдаёт результат

Причём на каждом уровне могут быть подуровни с искажением смысла и тратой ресурсов на межуровневое взаимодействие.

В идеале можно вообще выкинуть все или некоторые этапы, ну или хотя бы пытаться их оптимизировать.

Планы на будущее


Пока что мы можем упрощать взаимодействие пользователя с программой. В будущем работу программиста следует автоматизировать и включить в состав домашнего электронного помошника. Потому, что человеку достаточно просто приказать машине работать, более ничего не нужно.

Возможно, поначалу останутся дизайнеры, которые будут не без помощи компьютеров создавать интерфейсы к соцсетям, играм, купольным театрам, образовательным системам, виртуальным школам искусств и т.п. сущностям для неэффективной траты времени, которого у человека с приходом автоматизации станет гораздо больше. И в этом вопросе важны человеческая неидеальность и стремление всё сделать серьёзным и сложным, чтобы не пролистать свою ежедневную ленту в 1000 постов слишком быстро, а уж тем более посметь что-то из неё отфильтровать.

В современном мире в случае массового продукта очевидно, что единичные затраты одной команды транслируются на многомилионную армию пользователей. А значит, любая микрооптимизация становится весомой. И благодарность пользователей, обычно выражающаяся в готовности платить за новую версию, тут явно будет.

И если когда-либо в будущем мы встретим человека, который всё ещё читает мануалы, кликает по иконкам и работает с командной строкой, то будем точно знать, что это просто творческий человек. Может быть даже художник, которому запретили прибить себя к площади.

Относительно честное каррирование в JavaScript

Внимание! Я не являюсь знатоком функционального программирования, потому могу врать. Но вру я во благо, поскольку стремлюсь к упрощению жизни на практике, а не к идеальной теории.


Языки по-разному преподносят нам функции


В хороших языках программирования можно создать функцию от нескольких аргументов и "скармливать" им эти аргументы порциями. При этом "недокормленная" функция возвращает функцию, которая готова принимать оставшиеся аргументы. Ниже - соответствующий псевдокод:
func f (a,b,c,d) = a + b + c + d;
var f1 = f(1); // аналогично func f1 (b,c,d) = 1 + b + c + d;
var f2 = f1(6); // аналогично func f2 (c,d) = 1 + 6 + c + d;
var f3 = f2(2,5); // аналогично var f3 = f(1,6,2,5);


В некоторых остальных языках программирования можно использовать каррирование и частичное применение функций. Если полезная статья не врёт, каррирование в наших простонародных терминах - преобразование функции к такому виду, когда она может принимать аргументы по одному, а частичное применение - кормление функции несколькими первыми аргументами.
func f (a,b,c) = a + b + c;

var f1 = f (a); // вызывает ошибку

// каррирование:
var f_curried = curry(f); // создаёт функцию, способную принимать аргументы порциями
var f2 = f_curried(1)(6); // аналогично func f2 (c) = 1 + 6 + c;
var f3 = f2(2); // аналогично var f3 = f (1, 6, 2);

// частичное применение:
var fp = partial(f, 1); // создаёт функцию от 2 аргументов. аналогично func fp (b,c) = 1 + b + c;
var fp1 = fp(6); // вызывает ошибку
var fp2 = fp(6, 2); // аналогично var fp2 = f (1, 6, 2);


В ES5 есть Function.prototype.bind, которая позволяет нам использовать частичное применение.

var f = function(a,b,c,d){ return a+b+c+d; };
var f1 = f.bind(null, 1, 2); // аналогично var f1 = function(c,d){ return 1+2+c+d; };
var f2 = f1(4,5); // аналогично var f2 = f(1,2,4,5);

(bind первым аргументом принимает контекст - сущность, которая станет this внутри функции. При передаче null на месте this вроде бы оказывается глобальный объект.)

Можно нагуглить много интересного


С каррированием же всё сложно. Написав свою версию, я решил проверить, что вообще предлагается пользователю. В основном - рукотворные аналоги bind, пример со сложением двух чисел или статьи вида "не путайте каррирование и частичное применение". Из найденного мной только в одной статье была полезная реализация.
Я не прочитал ту статью полностью, я лишь просмотрел картинки примеры кода и некоторые пояснения, потому всё, что вы прочитаете далее, может совпадать с содержанием той статьи или противоречить ей.
Также радует библиотека Functional.js, познакомившись с которой, можно прекратить читать данную статью и заняться более важным делом. Там есть как минимум, ncurry.

Итак, если вы не видели подобного примера, вы ничего не гуглили по этой теме:
function add (x, y){
  return x + y;
}
 
function curry (x){
  return function (y){
    return x + y;
  }
}

Этим начинаются многие статьи про каррирование/частичное применение. Далее автор может написать о различии этих терминов, привести самописный аналог bind или вовсе закончить статью.

Долгий путь к каррированию


Я же предлагаю дойти до функции, которая позволит в JS скармливать аргументы порциями произвольным функциям. Для этого мы будем пользоваться замыканиями, в которых будем накапливать аргументы по мере их поступления до того момента, когда их будет достаточно для вызова функции.
В JS f.length - это количество аргументов функции f. Например, для add add.length == 2.

Попытаемся реализовать curry:
Не надо копипастить этот вариант. Ниже вы найдёте более правильный.
  1. function curry_i(f, that){
  2. var args = Array(f.length), n = 0;
  3. return function f_curried(){
  4. for(var i=0; i<arguments.length; ++i) args[i + n] = arguments[i];
  5. n += arguments.length;
  6. return n >= f.length ? f.apply(that, args) : f_curried;
  7. };
  8. }

curry_i принимает преобразуемую функцию f и объект that, который будет контекстом f при её вызове.
Здесь во второй строке мы заготавливаем массив args размера f.length для сохранения в нём переданных аргументов и инициализируем n, которое будет содержать количество уже переданных аргументов. Далее мы возвращаем f_curried, функцию, которая принимает аргументы порциями.
В строке 4 мы добавляем к args новые аргументы (те аргументы, которые получила f_curried). В большинстве реализаций arguments приводят к массиву с помощью Array.prototype.slice и соединяют с args с помощью Array.prototype.concat. Я же даю волю шаловливой императивной фантазии и использую цикл, наивно полагая, что так будет быстрее.
В пятой строке мы обновляем наш счётчик уже переданных аргументов.
В шестой строке, если количество аргументов не меньше того, которое требует функция, мы наконец вызываем нашу функцию, передавая ей накопленные параметры и возвращаем её результат. Если накопленных аргументов недостаточно, мы снова возвращаем f_curried, готовую принять новые аргументы.

Пример использования curry_i:
var f = function (a,b,c){ return a + b + c; };
var fc = curry_i(f);
console.log(fc(1)(2)(3)); // будет 6

Более того, поскольку f_curried принимает переменное число аргументов, можно использовать это по-разному:
var f = function (a,b,c){ return a + b + c; };
console.log(curry_i(f)(1)(2)(3)); // будет 6
console.log(curry_i(f)(1, 2)(3)); // будет 6
console.log(curry_i(f)(1)(2, 3)); // будет 6
console.log(curry_i(f)(1, 2, 3)); // будет 6

Получившаяся curry_i хорошо демонстрирует идею, но возвращаемая ей функция не является чистой. В args каждый раз исправно добавляются новые элементы, но возвращается всегда одна и та же f_curried. Поэтому, пока в args хранится недостаточное для вызова функции количество аргументов, curry_i(f) == curry_i(f)(1) == curry_i(f)(1)(2) == curry_i(f)(3)(...). Это значит, что после создания fc = curry_i(f) с каждым вызовом fc args будет увеличен. Т.е. будет такой эффект:
var f = function (a,b,c){ return a + b + c; };
var fc = curry_i(f);
 
fc(1);
fc(2);
console.log(fc(3)); // будет 6


Это выглядит сомнительно и не должно называться каррированием. Но я не убрал curry_i, поскольку она напоминает шаблон проектирования Builder. Действительно, fc можно понимать как объект-Builder, который строит объект x, получающийся при вызове f(a,b,c). Каждый вызов fc можно понимать как вызов метода "построить часть x", а автоматическое появление x при получении builderом достаточного количества параметров можно считать синтаксическим сахаром :)

Но ещё не всё потеряно. Чтобы возвращать каждый раз разные функции, следует создать ещё одну функцию, аргументы которой будут меняться при каждом вызове возвращаемой функции, которые будут отличать возвращаемые функции. Рассмотрим это на примере счётчика.

Вот он, подход, использовавшийся в неправильной curry_i:
function createCounter(){
  var n = 0;
  return function counter(){
    console.log(++n);
    return counter;
  }
}
 
var a = createCounter();
a();   // 1
a();   // 2
a()(); // 3, 4
a();   // 5

Возвращаемая функция counter изменяет переменную n из замыкания, переменную, привязанную к объекту a.

А вот - подход с возвращением каждый раз новой функции (вернее, функции с новым замыканием):
function createCounter(){
  return (function counter_maker(n){
    return function counter(){
      console.log(n + 1);
      return counter_maker(n + 1);
    }
  })(0);
}
 
var a = createCounter();
a();   // 1
a();   // 1
a()(); // 1, 2
a();   // 1

Здесь counter_maker создаёт новый экземпляр счётчика с начальным значением n, возвращая counter - функцию, при вызове которой создаётся новый счётчик. n привязано к возвращаемому значению, но не к a. Потому, при попытке вызвать a несколько раз подряд, мы не наблюдаем увеличения счётчика. Только при использовании результата выполнения a возможно использования счётчика.
Также здесь мы избавились от присваивания, заменив его на вызов функции с новым аргументом.

Используя подобную технологию, автор вышеупомянутой статьи создаёт прекрасную функцию curry. Вместе с количеством аргументов в аналог counter_maker автор передаёт массив с накопленными аргументами и массив с новыми. За счёт этого все функции чисты и curry делает то, что пользователь от неё ожидает. Но массивы постоянно копируются, и автор говорит, что при использовании замыканий копировать массивы в общем-то не надо, а все каррированные функции могут разделять один и тот же массив. Насколько я понял, нерусские выражения, такое решение автором не было найдено.

Вариант, который работает


Итак, для успешного завершения надо бы каждый раз фиксировать новый список аргументов, т.е. создавать новое замыкание для каждой возвращаемой функции, в котором бы хранился новый список аргументов.
Для этого можно передавать в аналог counter_maker "сумму" уже накопленного списка аргументов с полученными только что аргументами аналога counter.
Я с самого начала не хотел копировать массив аргументов, даже заранее задавал его длину. Поэтому при каждом вызове аналога counter в массив аргументов добавлялись свежеполученные аргументы, но фиксировалось число аргементов, а не они сами. То есть аналог counter_maker получал в качестве параметра текущее число аргументов. В итоге, при вызове counter аргументы у меня записываются, начиная с последней позиции, аналогично с curry_i. Но эта последняя позиция теперь зависит от возвращаемой функции, что позволяет затирать уже записанные аргументы при повторном вызове функции.
Поскольку моя реализация позволяет передать функции больше аргументов, чем надо (в любой момент, когда значение функции ещё не вычислено, можно передать лишние аргументы), массив аргументов может автоматически увеличиваться. Поэтому, перед вызовом функции он обрезается до текущего числа аргументов.

Рассмотрим это на примере (пусть fc = curry(f), f принимает 4 аргумента):
программа              args
fc(1, 2, 3, 4);      [1, 2, 3, 4]
fc(6, 5);            [6, 5, 3, 4] // поменялись первые два значения
fc(8);               [8, 5, 3, 4]
fc(1)(4);            [1, 4, 3, 4]
fc(0,2)(8);          [0, 2, 8, 4]
fc(9)(8)(7)(6, 5);   [9, 8, 7, 6, 5]
fc(3);               [3, 8, 7, 6, 5]
fc(7, 4, 5, 2);      [7, 4, 5, 2] // при вызове функции длина массива
                // уменьшилась до переданного на этом этапе количества


А теперь - сама функция curry:
Вот это - более хороший вариант.
Добавлено через несколько месяцев: Но он тоже не прокатит.
function curry(f, that){
  var args = Array(f.length), n = 0;
  return (function f_curried(n){
    return function(){
      for(var i=0; i<arguments.length; ++i) args[i + n] = arguments[i];
      var N = n + arguments.length;
      if(N < f.length) return f_curried(N);
      args.length = N;
      return f.apply(that, args);
    };
  })(0);
}


Здесь f_curried - аналог counter_maker, а безымянная функция - аналог counter. Массив args общий для всех, а N - новое значение количества накопленных аргументов.

Заметим, что если JS станет многопоточным, надо будет создать несколько fc = curry(f) - по одной на каждый поток.
Добавлено через несколько месяцев: Также, выполняя f = curry(fun); g = f(3); h = f(5); g(4), мы вызовем fun(5,4), а не fun(3,4), как планировалось. Всё же, оптимизация была лишней :( Правильный вариант лучше скопипастить из нерусской статьи.

Вот и всё


Следует выключить духовку, функции, готовые к простому с точки зрения пользователя частичному применению, можно подавать к столу:
var f = function (a,b,c,d) { return a + b + c + d; };
var fc = curry(f);
 
console.log(fc(1, 2, 3, 4)); // 10
console.log(fc(1, 2, 3)(4)); // 10
console.log(fc(1, 2)(3, 4)); // 10
console.log(fc(1)(2, 3, 4)); // 10
console.log(fc(1)(2)(3, 4)); // 10
console.log(fc(1)(2, 3)(4)); // 10
console.log(fc(1)(2)(3)(4)); // 10
console.log(fc(1, 2)(3)(4)); // 10
 
var f1 = fc( 5);
var f2 = f1(10);
var f3 = f2( 5);
var f4 = f3(10);
console.log(f4); // 30
 

Многострочные регулярные выражения в JavaScript

Не так давно мы выяснили, что с помощью редко используемых конструкций, Function.prototype.toString, обработки строк и eval или Function можно создать своё подмножество JS, которое при обработке средствами JS добавляет невиданные доселе возможности. Скажем, возможность запуска функций в синхронном стиле или возможность добавления препроцессора почти как в C.

Сегодня мы поговорим о гораздо более простых вещах. О том, что знает каждый из нас.

Многострочная строка


function line(f){ return f.toString().match(/\/\*([\s\S]*)\*\//)[1]; }

Самый простой способ сделать многострочную строку - записать строку внутри комментария, а затем вынуть её оттуда регулярным выражением. Конечно же, не без помощи Function.prototype.toString.
Кто-то использует для этого содержимое скрытых HTML-узлов, но подобный подход смотрится крайне странно при использовании Node.JS.
Кто-то пользуется стандартной возможностью, но ему приходится долго и упорно расставлять "\" и "\n".

А вот как используется функция line:
var test = line(function(){/*
  My
      multiline
                  string!
*/});
 
console.log(test); 
 
// выведет:
//
//  My
//      multiline
//                  string!
//
 

Заметим, что в строка начинается и кончается символами переноса строки. Избежать этого можно, немного усложнив регулярное выражение.

Многострочное регулярное выражение


Строка - это хорошо. Имея многострочную строку и парсер, можно "вклеивать" в код исходники на других языках (если заранее написан интерпретатор), небольшие HTML-страницы, простые таблицы и многое другое.

Полезно иметь возможность писать многострочные регулярные выражения и комментировать их. Сегодня я увидел такую возможность в каком-то языке и порадовался.
function createRegExp(func){
  if(typeof func !== 'function') throw new TypeError(func + ' is not a function');
 
  var m = func.toString(). // Да, мы наркоманы
    replace(/\s+|--.*?((?=\*\/)|$)/gm, '').
    match(/^.*?\/\*\/((?:\\\/|.)*?)\/(?:([img]+?)\/?)?\*\/\}$/);
 
  if(!m) throw new TypeError('Invalid RegExp format');
  return new RegExp(m[1], m[2] || undefined);
}

Функция createRegExp в качестве аргумента принимает функцию с закомментированным регулярным выражением. Оно может быть многострочным, иметь флаги и однострочные комментарии. Усложнив createRegExp, можно добавить многострочные комментарии, поддержку констант и своего микроязыка (например, for_all="[\s\S]+"; until_e="?e"; return /^ for_all until_e/;). Если хорошенько подумать над кодом и усовершенствовать возвращаемые значения (возвращать уже свой доморощенный объект), можно реализовать хоть рекурсивные условные переменные, в итоге позволив пользователю парсить XML получившимися "как-бы регулярками", если он осилит предложенный программистом способ записи грамматик...

А теперь вернёмся на Землю и продемонстрируем работу createRegExp на её же регулярных выражениях:
console.log(createRegExp(function(){/*
  /
      \s+               -- непустая последовательность пробельных символов (пробелы, переносы строки)
    |                   -- или
      - -               -- строки, начинающиеся на "--"
      .*?               -- содержащие некоторое количество символов
      (
          (?= \*\/ )    -- и заканчивающиеся либо символом закрытия комментария JS...
                        --   важно не удалить закрытие комментария JS вместе с комментарием регулярного выражения
                        --   иначе второе регулярное выражение из createRegExp не будет соответствовать строке
                        --   (поэтому и используется ?=, чтобы комментарий не захватывался)
        |               -- ...или
          $             -- концом строки
      )
  /
  g                     -- причём, замена по этому регулярному выражению будет выполнена несколько раз,
  m                     -- а $ символизирует конец подстроки
*/}), createRegExp(function(){/*
  /
    ^.*?\/\*            -- какие-то символы и открытие комментария в начале строки
    \/                  -- символ "/" - начало регулярного выражения
    ( (?: \\\/ | . )*? )-- сохраняем наименьшую последовательность строки "\/" или любых символов,
    \/                  -- идущих до символа "/"
    (?:([img]+?)\/?)?   -- если есть последовательность из букв i, m, g, вероятно, заканчивающаяся на "/",
                        -- сохраняем её
    \*\/\}$             -- конец регулярного выражения сопровождается концом комментария и
                        -- исходный код функции завершается
  /
*/}), createRegExp(function(){/*
  /^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/ -- тут всё очевидно, можно не расписывать
*/})
);
 
// выведет:
// /\s+|--.*?((?=\*\/)|$)/gm /^.*?\/\*\/((?:\\\/|.)*?)\/(?:([img]+?)\/?)?\*\/\}$/ /^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/
 

В более ранней версии createRegExp была возможность использовать символ # для начала комментария. Но при создании примеров выше, она утратила эту возможность. Действительно, как бы автор использовал "#" внутри создаваемого регулярного выражения?

И конечно же, ультратонкая версия createRegExp:
function crRegExp(f){var m;return RegExp((m=/^.*?\/\*\/((\\\/|.)*?)\/((.+?)\/?)?\*\/\}$/.exec((f+'').replace(/\s+|--.*?((?=\*\/)|$)/gm,'')))[1],m[4]||'');}

Lazy Initialization (design pattern): JavaScript realization

Таки не давала мне покоя сегодня ночью отложенная реализация. Пришлось начать что-то писать.

Итак, мы имеем LazyClass, который имеет публичный метод lazyMethod, который должен при первом вызове провести ещё и инициализацию объекта.

  1. function LazyClass(/* аргументы конструктора */){
  2. // инициализация, приватные поля и методы
  3. // ...
  4. }
  5. LazyClassExample.prototype.lazyMethod = function(/* аргументы lazyMethod */){
  6. // хочется в первый раз инициализировать объект
  7. }
  8.  


Вариант с if (не инициализировано) инициализируем() абсолютно ясен и недостаточно извращёнен для нас.
Можно создать публичный метод lazyMethod у экземпляра класса, который перекроет метод прототипа.
Этот lazyMethod будет инициализоровать наш объект, выполнять сам lazyMethod и удаляться, таким образом, во следующий раз будет вызываться lazyMethod прототипа.
Это позволит нам создать lazyMethod, который делает только то, что он должен делать, что бы он делал без применения отложенной реализации, т.е. сохранить разделение метода и инициализации. Ну и конечно же, мы получим удовольствие от процесса.

  1. function LazyClass(){
  2. // тут пропишем неотложную инициализацию
  3. this.lazyMethod = function(){ // "одноразовый" метод
  4. // тут пропишем ту часть инициализации, которую хотим отложить
  5. this.constructor.prototype.lazyMethod.apply(this,arguments); // вызываем lazyMethod прототипа
  6. delete this.lazyMethod; // удаляем "одноразовый" метод
  7. }
  8. }
  9. LazyClass.prototype.lazyMethod = function(){
  10. // делаем уже что-нибудь полезное
  11. }
  12.  


Как видим, lazyMethod прототипа не знает, что его подменяют, а фэйковый lazyMethod не знает подробностей реализации своего настоящего собрата.
Но вот LazyClass знает про алгоритм использования Lazy Initialization, а программисту придётся ещё раз прописать все трюки для второго класса. Мы любим извращения, но не ценим их в подобном копировании. Ещё и ошибиться можно.

Решено. Создаём нечто, что знает про Lazy Initialization и не знает про тех, кто будет это использовать.
Например, функция addLazyMethod. Она принимает экземпляр класса, функцию, которая должна заниматься инициализацией и имя метода, для которого будет использован сей механизм.

  1. function addLazyMethod(obj, init, MethodName){
  2. obj[MethodName] = function(){ // создать фэйковый метод у obj
  3. init.apply(obj, arguments); // вызвать init, прикрепив к obj и дав все аргументы
  4. obj.constructor.prototype[MethodName].apply(obj, arguments); // вызвать настоящий метод, будто ничего и не было
  5. delete obj[MethodName]; // удалиться, пока не поздно
  6. }
  7. }


И вот так эту функцию будет использовать наш класс:

  1. function LazyClassExample(/* аргументы конструктора */){
  2. addLazyMethod(this, function(){
  3. // то, что вызовется при первом вызове метода lazyMethod
  4. // данная функция имеет доступ к this и получает аргументы, переданные lazyMethod
  5. },'lazyMethod'); // имя метода, использующего отложенную инициализацию
  6. // инициализация, приватные поля и методы
  7. // ...
  8. }
  9. LazyClassExample.prototype.lazyMethod = function(/* аргументы lazyMethod */){
  10. // собственно, дейсвия с объектом.
  11. // вызовется каждый раз при вызове lazyMethod
  12. }


В итоге, нам нужен лишь класс, который имеет хотя бы один метод и желание написать addLazyMethod с нужными параметрами. Правда, всё это неприменимо для тех, кто как и я, не любит ООП.

Допустим, у нас есть глобальная функция func и мы хотим вызвать что-нибудь перед первым её вызовом. Создавать для столь низменных целей класс - дело кощунственное и сомнительное. Хотя бы потому, что мы накладываем новые требования "программист должен написать класс с не менее, чем одним методом". Мы возвращаемся к началу данного размышления, когда создавали фэйковое поле вручную.

addLazyMethod пользовалась сокрытием метода прототипа. В случае с глобальной функцией мы будем её честно наглым образом подменять на функцию, которая вызовет инициализатор, честную функцию и подменит всё обратно.
Идея выглядит примерно так: (и да, это только идея, не пытайтесь копировать этот код)

  1. function addLazyInitialization(func, init){ // хотелось бы как-то так
  2. var funcCopy = func; // сохраняем функцию
  3. func = function(){ // переопределяем функцию
  4. init.apply(this, arguments); // инициализация
  5. funcCopy.apply(this, arguments); // вызываем настоящую функцию
  6. func = funcCopy; // возвращаем всё на место
  7. }
  8. }


Всем идея хороша, только мы подменили аргумент func, а не саму функцию, т.о. этого никто и не заметил.
И да, с this могут быть проблемы в зависимости от того, как вызывается addLazyInitialization и где находится func.
Но таки этот неправильно работающий пример иллюстрирует всю правильную суть безумной идеи.
Нам придётся вернуться к сигнатуре, аналогичной addLazyMethod:

  1. function addLazyInitialization(obj, init, funcName){
  2. var funcCopy = obj[funcName];
  3. obj[funcName] = function(){
  4. init.apply(obj, arguments);
  5. funcCopy.apply(obj, arguments);
  6. obj[funcName] = funcCopy;
  7. }
  8. }


Теперь можно "подшутить" над глобальной функцией func, над функцией func внутри какого-нибудь массива и даже просто исправить наш пример использования addLazyMethod классом LazyClass, просто заменив addLazyMethod на addLazyInitialization.

  1. function tst(a){ // глобальная функция, над которой хотим подшутить
  2. console.log('tst '+a);
  3. }
  4.  
  5. addLazyInitialization(this, function(){
  6. // инициализация
  7. console.log('init '+arguments[0]);
  8. },'tst');
  9.  
  10. A = {};
  11. A.a = function(a) { console.log('a ' + a); } // функция, над которой хотим подшутить
  12.  
  13. addLazyInitialization(A, function(a){
  14. // инициализация
  15. console.log('init a ' + a);
  16. },'a');
  17.  
  18. // и таки уже проверим всё это:
  19.  
  20. tst(2); // вызовется фэйковая функция
  21. tst(4); // вызовется настоящая функция
  22. tst(6); // вызовется настоящая функция
  23.  
  24. A.a(2); // вызовется фэйковая функция
  25. A.a(4); // вызовется настоящая функция
  26. A.a(6); // вызовется настоящая функция


Честно говоря, в прошлые разы можно было тоже проверить, выполнив нечто подобное

  1. var x = new LazyClass();
  2. x.lazyMethod(); x.lazyMethod(); x.lazyMethod();


Хотя, надо было создать ещё один объект и проверить, что состояние подмены метода lazyMethod для первого объекта не зависит от состояния подмены для второго. Если Вы не поняли, к чему это я, то представьте, что мы подменяем не lazyMethod, а LazyClass.prototype.lazyMethod. Впрочем, подобное будет полезно, если мы хотим сделать отложенную инициализацию класса. Скажем, экземпляр класса предоставляет доступ к одному из байтов первого сектора диска. При обращении к какому-либо байту будет считан сектор и инициализирован весь класс. Безумный пример, да. Впрочем, добавим ещё безумия. Каждый байт зашифрован с помощью сложного алгоритма, т.е. метод getByte должен будет при первом вызове расшифровать байт и сохранить результат расшифровки.

И да, надо заметить, что поскольку addLazyInitialization не делает delete, она оставляет после себя метод lazyMethod у экземпляра класса, который делает то же, что и LazyClass.prototype.lazyMethod.
По сути, если после этого изменить LazyClass.prototype.lazyMethod, те экземпляры класса LazyClass, которые уже вызвали хоть раз lazyMethod, изменений не заметят и будут вызывать старый lazyMethod. Тем прекрасна addLazyMethod, что она такой пакости не сделает.

Вернёмся к безумному примеру. Т.е. нам надо заменить Byte.prototype.getByte при первом вызове метода для всего класса, а затем - конкретный Byte.getByte на нерасшифровывающий, причём недопустима подстановка своего getByte в конструкторе, как бы сделала addLazyMethod, поскольку если подставлять getByte, который считывает и расшифровывает, то считывание сектора произойдёт при первом вызове для каждого экземпляра, а если getByte, который только расшифровывает, то никогда не вызовется метод, который считывает.

Следовательно, надо создать Byte.prototype.getByte, который {
    считает сектор;
    заменит себя на getByte #1, который {
        расшифрует байт и закэширует его;
        заменит копию себя для отдельного экземпляра на getByte #2, который {
            возвратит кэшированный байт;
        }
        возвратит getByte() #2;
    }
    возвратит getByte() #1;
}

Т.о. объекты, созданные до первого вызова getByte, будут иметь возможность вызвать Byte.prototype.getByte,
после первого вызова getByte сектор будет считан, старые и вновь созданные, не вызывавшие getByte, будут иметь возможность вызвать Byte.prototype.getByte #1,
все, когда либо вызвавшие getByte будут иметь возможность вызвать свой getByte #2.

И всё тут хорошо, только у каждого объекта будет свой getByte.
И да, при изменении Byte.prototype.getByte объекты, хоть один раз вызвавшие getByte, не будут видеть изменений, а невызвавшие - будут.
Сложно преодолеть дискриминацию по факту вызывания и соответствующее игнорирование объектами изменений Byte.prototype.getByte, если использовать извращённую логику данной статьи. Конечно, добавление поля с флагом факта вызова позволит легко всё обойти, но в самом начале подобное было отвергнуто по причине излишней очевидности.

Можно вернуться к идеям, изложенным выше и при конструировании объекта подсунуть экземпляру фэйковый метод getByte, который {
    вызовет Byte.prototype.loadByte и получит байт;
    расшифрует байт и закэширует его;
    удалит себя, открыв Byte.prototype.getByte #1, который {
        возвратит кэшированный байт;
    }
    возвратит getByte #1();
}

Причём, здесь введён Byte.prototype.loadByte, который {
    считывает сектор;
    заменяет себя на loadByte #1, который {
        возвращает нерасшифрованный байт;
    }
    возвращает loadByte #1();
}

Теперь при самом первом вызове фэйковая getByte вспомогательная Byte.prototype.loadByte считает сектор и заменится на Byte.prototype.loadByte #1, для вызвавшего объекта байт будет расшифрован и удалится фэйковая getByte, заменившись на Byte.prototype.getByte #1. Объект станет вызывать Byte.prototype.getByte #1 и давать кэшированный байт.
Для следующего объекта вызовется фэйковая getByte, получит и расшифрует байт от Byte.prototype.loadByte #1, а потом удалится, открыв Byte.prototype.getByte #1. Объект станет вызывать Byte.prototype.getByte #1 и давать кэшированный байт.

Теперь присвоим что-нибудь Byte.prototype.getByte. Если это было сделано до самого первого вызова getByte, естественно всё хорошо. Если после (учтём, что поведение loadByte нас не затрагивает), то вызвавший объект будет всегда вызывать Byte.prototype.getByte (getByte #1) и подмену заметит, а невызвавшие - вызовут и переключатся на Byte.prototype.getByte (getByte #1), т.о. все объекты подмену заметят.

А вот Byte.prototype.loadByte уже так не изменишь.
Впрочем, менять метод считывания байтов на протяжении исполнения - дело весьма сомнительное. Зато, в отличие от первого варианта, мы можем выключить getByte сразу для всех экземпляров в любой момент. В первом же варианте "выключились" бы только нечитанные байты.
А если, скажем, у нас есть два потребителя байтов, но один из них любит получать их В ВЕРХНЕМ РЕГИСТРЕ, то, изменив Byte.prototype.loadByte, можно переключить режим, приспособившись к нужному потребителю.

Вот так, уважаемые читатели, изгнав очевидное решение с выполнением по условию, мы извратились и решили ещё одну несуществующую проблему, создав функции addLazyMethod, addLazyInitialization и некоторые размышления по поводу подмены подмен. Впрочем, усилия наши не напрасны. Теоретически, если функция/метод вызовется очень-очень большое количество раз, экономия на непроверке условия даст нам совсем чуть-чуть дополнительного времени.
И всё это - не считая огромного удовольствия...

А теперь - тестирование


Как известно, в наше время ближайший интерпретатор JavaScript есть в браузере. Что же, можно поддаться этому веянию моды и использовать доступное.

Первая беда: из-за того, что логика простой проверки флага была отброшена, использование addLazyInitialization для глобальной функции, а затем вызов её в цикле, приводит к многократному вызову init(), поскольку кроме программиста код ещё и интерпретатор в Chrome оптимизирует :( Потому, на других браузерах данная штука не запускалась.

А вот, addLazyMethod, конечно же, работает.
Для теста создадим два класса - с использованием addLazyMethod и с проверкой флага. Выполним методы каждого класса по миллиону раз и измерим время.
Для проверки "устаканивания" запустим "миллионы" несколько раз.

function class_1(){
	addLazyMethod(this, function(){
		console.log('func_1.init');
	}, 'func');
}
class_1.prototype.func = function(){
	return 0;
}
 
function class_2(){
	this.i = true;
}
class_2.prototype.func = function(){
	if(this.i){
		this.i=false;
		console.log('func_2.init');
	}
	return 0;
}
 
window.__defineGetter__('time', function (){ return new Date().getTime(); });
 
var n = 1000000;
console.log(n+' iterations');
 
function test(obj){
	var t0 = time;
	for(var i=0;i<n;++i)obj.func();
	console.log(obj.constructor.name+': '+(time-t0));
}
 
c_1 = new class_1();
c_2 = new class_2();
 
var N = 20;
 
for(var i=0;i<N;++i){
	test(c_1);
	test(c_2);
}
 


В Chrome (18.0.1025.168 m) для class_1 время вызова миллиона функций составило сначала около 3х секунд, а затем "устаканится" до порядка 300мс.
Для class_2, который использует просто флаг, время оказалось почти константное и весьма малое - около 10мс.
Chrome сам разберётся со скриптами, не надо ему мешать.

А вот Opera (11.64) радует наркоманов-оптимизаторов. Все времена почти константны и составляют около 1.4с для class_1 и около 1.5с для class_2.
Инкапсулировав алгоритм Lazy Initialization, мы ещё получили целых 0.1мкс с каждого вызова. Хотя, давайте не забывать, что функции наши почти ничего не делали, т.е. порядка 1мкс Opera тратит только на вызов метода. Вставим в тело функций что-то серьёзное - и никто не заметит разницы.

Firefox (12.0) же вообще не замечает разницы. Что для class_1, что для class_2, миллион вызовов прошёл за 260 плюс-минус 10 миллисекунд.
И если в Хроме был замееееетный перевес в сторону здравого смысла, Опера медленно и неуверенно указала на наркоманство, то Огнелису вообще на всё до лампочки. Как ни крути, всё заработает.

IE (9.0.8112.16421) не понял __defineGetter__ и func.name, но после переписывание time в функцию сказал, что за 600-700мс всё выполняется, причём class_1 отнял времени меньше в среднем от 20 до 80мс. Т.е., если говорить об относительных величинах, повёл себя как Opera, разве что быстрее в 2 раза.

Что же, столько было продумано, получены ценные сведения, но таки в очередной раз мы убеждаемся, что курение вредит нашему здоровью.

Дневник задрота

Прочитав парочку заметок satanamax по поводу дневника практиканта, решил я, что тоже надо б начать.

Итак, дневник задрота. Поскольку я в НГУ уже одиннадцатый месяц с утра (сцуко) до вечера, будет мало и редко. Может быть, это даже последняя запись из дневника задрота. Кто знает.

Итак, решил я сегодня уж точно написать так как мне дали ДВА счастливых билета. Повезло, мать вашу! Стоит лишь только напомнить, что вероятность получения счастливого билета - около 5% (раньше на паскале программку писал по этому поводу). А тут - два. И один - блатной. 068428 и 789987! Вероятность такого блатного - 0.1%. Ура!
А получить такие билеты в один день - 0.005%, т.е. 1:20000.



Ныне я по долгу службы, а если посмотреть глубже, то для собственного же блага учу PHP. Дали мне ботанские чудо-файлы. Там есть теория и есть задания. Описано всё просто превосходно. И задания интересные. Кстати, там ещё такое же по HTML. Теперь кроме основных тегов буду что-нибудь ещё знать.

Итак, сегодня я поделюсь своими впечатлениями. Пока что я в PHP только начинающий, потому почитать потом будет интересно.

Итак, моменты и впечатления:

  1. все переменные начинаются с доллара: первый день: какая печаль, везде доллары! Второй день: хм... так это ж удобно делать print
    print "Здравствуй, пользователь $username";
    Все дни: for($x=0;x<count($arr);++$x) - аааа, почему зациклилось??
  2. $_POST и $_GET - суперглобальные (если не ошибаюсь) массивы, в которые попадает то, что передали странице: круто! *.php файлы могут общаться!!
  3. в каждой функции надо прописать global для глобальных переменных, иначе создадутся локальные: ну, прекрасно, хоть и неудобно. Идея разделения хороша. Любимый $x не перетрётся внутри функции.
    foreach($fields as $f){
      global $$f;
       if($$f!="")..........

    Вот тут я удивился. $$f - это как будто мы поставили доллар перед строкой $f, то есть используем переменную, обращаясь к ней по имени, записанном где-то там. Ну да, свой калькулятор я писал. Понимаю, что в обращении по имени к переменной ничего плохого нет, но global $f подняло меня на следующий уровень абстракции.
  4. foreach - гламурный цикл for: странно... вот в javе он просто for и пишется (кажется), и ничего не мешает. И тут бы не мешало: for($arr as $elem).
  5. сразу впечатления: не надо прописывать тип переменной, не надо объявлять переменные, присваивай кому хочешь что хочешь. Возвращай в функциях что хочешь.
    Ну, после javascriptа это не так шокирует, хотя терзает проблема накладных расходов при преобразовании типов и страх потерять переменную.
  6. if(!eregi("^[a-z ]+$",$name))$error['name']=true;
    if($error){...
    if($error['name'])...

    Ассоциативные массивы! Можно проверить, есть ли массив! Какая прелесть!
    Регулярные выражения! Давно хотел их заботать. Правда, не заботал пока.
  7. Доступ к базам данных! mysql_query("SELECT * FROM $table"); какая прелесть!!


А ещё я решил на обеде гулять в лесу. Нет, ну правда. Я ж не поехал на дачу. Хотя, на даче мама бы говорила "клещи, клещи" и не давала бы в лес ходить. А я тут хожу.
Так что, если я вдруг помру, знайте: в лесу около НГУ ходить опасно.
P.S. Сделал сегодня несколько фотографий, надеюсь, выложу скоро. Или не скоро.
P.P.S. Печалит меня отпуск taaaskeeeteee. В жж станет пусто и это плохо.

Буфер обмена и консоль - кто кого?

Несколько десятков минут назад я написал про текстовый редактор. Там упоминались весёлые проблемы с буфером обмена, здесь же я расскажу о своих приключениях.

1. Буфер обмена Windows. Ну и как с ним работать?
Естественно, ищем в сети примеры. Оказывается, что существует стандартный код, написанный по-видимому, нашими дедами или дедами наших дедов. Код этот является священным и каждый уважающий себя программист, желающий поделиться своими знаниями, должен скопировать его и разместить в своём изложении. Кстати, не важно, что он пишет сам, но код этот вставить - святое дело. Так, на одном сайте сказали, проверяйте, мол, есть ли в буфере текст, дабы другой тип данных случайно не схватить. А пример кода - тот же самый, дедовский, без проверки.

//Пример записи и чтения текста.
CString source; //в эту переменную нужно записать текст, который в дальнейшем поместится в буфер обмена
//запись текста в буфер обмена
if(OpenClipboard())//открываем буфер обмена
{
  HGLOBAL hgBuffer;
  char* chBuffer;
  EmptyClipboard(); //очищаем буфер
  hgBuffer= GlobalAlloc(GMEM_DDESHARE, source.GetLength()+1);//выделяем память
  chBuffer= (char*)GlobalLock(hgBuffer); //блокируем память
  strcpy(chBuffer, LPCSTR(source));
  GlobalUnlock(hgBuffer);//разблокируем память
  SetClipboardData(CF_TEXT, hgBuffer);//помещаем текст в буфер обмена
  CloseClipboard(); //закрываем буфер обмена
}

//чтение текста из буфера обмена
CString fromClipboard;//в эту переменную сохраним текст из буфера обмена
if ( OpenClipboard() )//открываем буфер обмена
{
  HANDLE hData = GetClipboardData(CF_TEXT);//извлекаем текст из буфера обмена
  char* chBuffer= (char*)GlobalLock(hData);//блокируем память
  fromClipboard = chBuffer;
  GlobalUnlock(hData);//разблокируем память
  CloseClipboard();//закрываем буфер обмена
}

2. Ну, OK, всё работает?
Копируем код, всё будто бы работает. Естественно, вставленная картинка вызывает падение программы, т.к. хочу я текст, а в дедовском примере проверки нет.
Ищем проверку. Всё работает. Хотя, нет. Когда я получаю русские буквы из внешнего мира, всё нормально, но внешний мир не хочет их получать.
Странно? Ага. Смотрим статью Кракозябры в Википедии. Опытным путём выясняется, что CP866 внешний мир понял как CP437. Печаль... Оказывается, надо настраивать CF_LOCALE. На RSDN находится ответ:

BOOL res = OpenClipboard();
if(res) {
  EmptyClipboard();
  char* clip_data = reinterpret_cast<char*>(GlobalAlloc(GMEM_FIXED, MAX_PATH));
  lstrcpy(clip_data, "Мой супер-пупер текст");
  SetClipboardData(CF_TEXT, reinterpret_cast<HANDLE>(clip_data));

  LCID* lcid = reinterpret_cast<DWORD*>(GlobalAlloc(GMEM_FIXED, sizeof(DWORD)));
  *lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL), SORT_DEFAULT);
  SetClipboardData(CF_LOCALE, reinterpret_cast<HANDLE>(lcid));

  CloseClipboard();
}

Берём здешние идеи, пишем, и... внешний мир радуется!

3. Теперь-то уж точно всё?
Оказывается, нет. Если программист не позаботился, CF_LOCALE выставит Windows. Выставит в соответствии с раскладкой клавиатуры и своими понятиями о происходящем.
Итак, приложение копирует юникодовский текст в буфер обмена. Если раскладка русская, CF_LOCALE говорит "по русски", если нет, то CF_LOCALE даёт нам понять, что приложение мыслит по-иностранному. Юникоду на это наплевать. Там, как были русские буквы, так и остались. А вот при неявном преобразовании CF_UNICODETEXT в CF_OEMTEXT (подобные неявные преобразования являются большим плюсом буфера обмена, т.к. обеспечивают большую совместимость без особых мучений программиста) учитывается громкое слово CF_LOCALE, и все русские буквы теряются, т.к. юникод преобразуется в какую-нибудь европейскую кодировку, которая и знать не знала о наших буквах.
(s.edit 1.0.0.1 как раз подобное демонстрирует, даже говорит пользователю "вот сейчас я те покажу")
На данный момент наиболее позитивное решение для меня - взять юникод из буфера обмена и преобразовать его в CP866 при помощи CharToOem.
BOOL WINAPI CharToOem( __in LPCTSTR lpszSrc, __out LPSTR lpszDst );
Если придумаю нечто более разумное - сообщу :)

Получаются примерно такие штуки:
(возможны ошибки, т.к. получено это изменением кода s.edit, а там всё выглядит немного не так, т.е. данный код я и не пытался компилировать, тут только идеи)

void copy(char *buf){
  HGLOBAL hgBuffer, _lcid;
  LCID* lcid;
  char* chBuffer;
  if(!OpenClipboard(0))return;
  EmptyClipboard();
  hgBuffer= GlobalAlloc(GMEM_MOVEABLE, strlen(buf)+1);
  if(!hgBuffer)goto copy_end;
  chBuffer= (char*)GlobalLock(hgBuffer);
  strcpy(chBuffer,buf);
  GlobalUnlock(hgBuffer);
  SetClipboardData(CF_OEMTEXT, hgBuffer);
  _lcid = (DWORD*)(GlobalAlloc(GMEM_MOVEABLE, sizeof(DWORD)));
  if(!_lcid)goto copy_end;
  lcid=(DWORD*)GlobalLock(_lcid);
  *lcid = MAKELCID(MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL), SORT_DEFAULT);
  GlobalUnlock(_lcid);
  SetClipboardData(CF_LOCALE, _lcid);
copy_end:
  CloseClipboard();
}

void paste_unicode(char *buf, size_t max_len){
  HANDLE hData;
  char* chBuffer;
  wchar_t* _unicode;
  if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return;
  if(!OpenClipboard(0))return;
  hData = GetClipboardData(CF_UNICODETEXT);
  _unicode= (wchar_t*)GlobalLock(hData);
  chBuffer=(char*)malloc(wcslen(_unicode)+1);
  if(!chBuffer)goto paste_end;
  if(!CharToOem(_unicode,chBuffer))goto paste_end;
  strcpy(buf,chBuffer,max_len);
paste_end:
  free(chBuffer);
  GlobalUnlock(hData);
  CloseClipboard();
}

void paste(char *buf, size_t max_len){
  HANDLE hData,_locale;
  char* chBuffer;
  DWORD* locale;
  if (!IsClipboardFormatAvailable(CF_OEMTEXT)){
    if(IsClipboardFormatAvailable(CF_UNICODETEXT))paste_unicode(buf,max_len);
    return;
  }
  if(!OpenClipboard(0))return;
  if(IsClipboardFormatAvailable(CF_LOCALE)){
    _locale=GetClipboardData(CF_LOCALE);
    locale=(DWORD*)GlobalLock(_locale);
    if(PRIMARYLANGID(*locale)!=LANG_RUSSIAN){
      if(IsClipboardFormatAvailable(CF_UNICODETEXT)){
        GlobalUnlock(_locale);
        GlobalUnlock(hData);
        CloseClipboard();
        paste_unicode(buf,max_len);
        return;
      }
    }
    GlobalUnlock(_locale);
  }
  hData = GetClipboardData(CF_OEMTEXT);
  chBuffer= (char*)GlobalLock(hData);
  strncpy(buf,chBuffer,max_len);
  GlobalUnlock(hData);
  CloseClipboard();
}

Напоминаю, что данные примеры позволяют работать приложению с CP866 с буфером обмена Windows. Для работы с CP1251, по видимому, надо заменить CF_OEMTEXT на CF_TEXT.
Кстати, если использовать CharToOemBuff, опять же, по-видимому, отпадёт необходимость в излишнем копировании в chBuffer.
И ещё раз "по видимому": по видимому, без комментариев код и так понятен, т.к. некоторые действия были описаны выше, а названия функций очевидны. Тут стоит лишь добавить, что buf - строка с данными, информация из которой копируется в буфер обмена в copy и в которую вставляется информация в paste. max_len - ограничение по памяти для buf. В ином случае, в paste и paste_unicode вместо копирования следует узнавать strlen(chBuffer), вернее, просить strlen(chBuffer)+1 байт памяти и копировать.
Выглядеть будет примерно так:

char* paste(){// изменили код
  char *buf;// добавили строку

  ...// все переменные и действия те же, только вместо return пишем return 0 и в paste_unicode(...) заменяем на return paste_unicode(), paste_unicode тоже переписываем

  chBuffer = (char*)GlobalLock(hData);
  buf = (char*)malloc(strlen(chBuffer)+1);// добавили строку
  if(buf)strcpy(buf,chBuffer);// изменили код
  GlobalUnlock(hData);
  CloseClipboard();
  return buf;// добавили строку
}

Вот так примерно. Хотя, это лишь мои мысли, да. За более чёткими сведениями обращайтесь к профессионалам.