Ruby/Идеология: различия между версиями

Материал из Викиучебника — открытых книг для открытого мира
Содержимое удалено Содержимое добавлено
Нет описания правки
→‎Злые волшебники: Сделал подразделом Словарика философствующего программиста
Строка 253: Строка 253:




=== Злые волшебники ===

Никто не может отрицать — создавать приложения становится все сложнее и сложнее. В частности, пользовательские интерфейсы становятся все более утонченными. Двадцать лет назад приложение среднего масштаба обошлось бы интерфейсом "стеклянного телетайпа" (а может быть, интерфейса не было бы и вовсе). Асинхронные терминалы обеспечивали интерактивное отображение символов, а устройства ввода (наподобие вездесущей IBM 3270) позволяли набирать целую экранную страницу перед нажатием клавиши SEND. Теперь пользователи требуют графический интерфейс с контекстно-зависимой справкой, средствами типа "вырезать и вставить", "перетащить и отпустить", средством OLE, много- или одно-документным интерфейсом. Пользователям потребна интеграция с web-браузером и поддержка архитектуры с тонким клиентом.

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

Вам нужно приложение с многодокументным интерфейсом и поддержкой контейнера OLE? Один щелчок мыши, ответ на пару простых вопросов и функция-мастер автоматически сгенерирует для вас скелет программы. При выполнении данного сценария среда Microsoft Visual C++ автоматически создает программу, содержащую свыше 1200 строк. Функции-мастера хорошо справляются и с другими заданиями. Вы можете воспользоваться мастерами при создании серверных компонентов, реализации Java beans, работе с сетевыми интерфейсами — все это достаточно сложные области, где не обойтись без помощи эксперта. Но применение функции-мастера, спроектированной неким компьютерным гуру, не делает автоматически из разработчика Джо компьютерного эксперта. Джо чувствует себя недурно — он ведь сгенерировал большое количество исходного текста и довольно элегантную на вид программу. Ему нужно лишь добавить функциональную возможность, характерную для данного приложения, и программу можно отправлять заказчику. Но покуда Джо реально не осознает сути программы, сгенерированной от его имени, он вводит самого себя в заблуждение. Он программирует в расчете на стечение обстоятельств.

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

''Не пользуйтесь программой функции-мастера, которую не понимаете''

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

'''Данная статья нагло использует материалы книги "Программист-прагматик" Эндрю Ханта и Дэвида Томаса'''





Версия от 00:22, 20 февраля 2007

Здесь авторы высказывают и иногда даже пытаются обосновать свое видение на некоторые идеологические темы программирования.

Почему скорость написания программы важнее скорости ее выполнения?

Ответ заключается в следующем вопросе (сказывается национальность автора): вам платят за результат, который достигнут в срок или за качество, которое достигнуто с опозданием?

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

Вот именно поэтому код стоит писать быстро. И только, если он будет неприемлемо медленный, его будут переделывать (оптимизировать). Но скорее всего это будете делать не вы. Есть над чем задуматься?

Rubynovich, 30 мая 2006

Дело не только в личной выгоде программиста

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

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

Есть ещё такое общее, но очевидно верное идеологическое утверждение: между программистом и вычислительной машиной существует непреодолимый рубеж понимания. Старание писать эффективную для машины программу предполагает каждый раз и для каждой новой программистской задачи «лезть вон из кожи» человеческой, и стараться угодить машине; отвлекаться от привычной человеческому созданию картины мира (предмет, категория, отношение, мера, закономерность, причинность) и вникать в привычные для вычислительной машины понятия (чтение, запись, переход, сравнение). Способности человека в этом отношении довольно скудны[1], поэтому такая практика по самой своей природе расточительна, неразумна. Человек должен излагать свои замыслы на понятном ему языке, и при всякой возможности автоматизировать их перевод на язык вычислительной машины. Лучший способ достичь этого — с умом писать эффективные, оптимизированные под машину, программы, но только для построений, используемых людьми в процессе решения уже конкретной задачи на языке, оптимизированном под людей. Именно это делается при оптимизации компиляторов и (как в случае Руби) интерпретаторов высокоуровневых языков. Тяжкий труд перевода человеческих мыслей в машинные следует делать раз и навсегда, а не каждый раз заново.


  • ^  — Проверить это можно, например, с одной стороны, попытавшись написать на Ассемблере программу, умеющую распознавать рукописный текст, а с другой стороны — сыграть в шахматы на время со специальной (сравнительно простой) компьютерной программой. Для дальнейшего чтения: Tor Nørretranders, Mærk verden, 1998.

Ramir, 9 июня 2006

Эволюция программ: от столбцов к строкам

Задумайтесь над следующими вопросами:

  • Почему приятней сидеть за широкоформатным монитором?
  • Почему никто не разворачивает монитор на 90°?
  • Почему глаза человека расположены вдоль горизонтальной плоскости, а не вертикальной (например: на лбу и подбородке)?

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

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

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

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

Подведем итог:

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

В результате мы имеем все основания предполагать, что чтение кода на языке Ассемблера происходит медленней, чем чтение кода на языках более высокого уровня. Естественно, что в этом случае надо считать не строчками, а количеством прочитанного кода (знаками).

Вывод, который напрашивается сам собой:

  • Идеальная программа равномерно заполняет своим исходным кодом широкоформатный экран.

Славо Богу, что идеал недостижим. =)

Rubynovich 14:33, 30 мая 2006 (UTC)

Куда подевались циклы

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

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

Rubynovich 15:03, 30 мая 2006 (UTC)

Долой циклы! Даешь итераторы!

На сайте Рогановой прочитал примерные задания по предмету "Информатика" для инженерных специальностей. Почти все задания используют циклы. Отсюда вопрос: стоит ли использовать циклы при наличии итераторов?

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

m, n = 2, 2 
while m <= 5 
    m, n = m + 2, n + m 
end 
puts "m=#{m}" 
puts "n=#{n}"

Ее можно переписать в виде:

n = 2 
2.step(5,2){ |m| n += m } 
puts "m=#{6}\nn=#{n}"

Стоит заметить, что во втором фрагменте мы избавились от ненужной глобальной переменной, которую превратили в локальный счетчик итератора. При этом была использована информация о конечном значении счетчика. Стоит ли использовать циклы, если они порождают массу проблем? А именно:

  • Ухудшение читабельности кода.
  • Сложность отладки.
  • Потребность в глобальных переменных.
  • Невнятные граничные условия.
  • Возможен бесконечный цикл.
  • Сложность в оценке производительности алгоритма.
  • и еще много еще чего.

Лично я, в своей практике, хочу преподавать студентам циклы в контексте устаревших конструкций (наряду с goto). Есть правда моменты, когда нельзя обойтись без циклов... но согласитесь, что таких ситуаций очень мало. Чаще циклы приносят только головную боль.

Почему исчезают переменные

Есть код с пятью переменными и код с десятью. Какой проще запомнить? Конечно же с пятью, потому, что... их меньше. Чем меньше данных нам приходится запоминать, тем вероятнее, что мы их действительно запомним. Идеальный вариант: ни одной переменной (только параметры методов и итераторов). Тогда и запоминать ничего не надо.

Для чего же используют переменные? Для сохранения промежуточного результата. Но если вы посмотрите на свои программы, то заметите, что очень часто переменные используются всего один-два раза. Скорее всего от таких переменных можно избавиться (заменив переменную ее значением). Более того, от них нужно избавляться.

Идеальной считается программа из одной строчки: она считывает данные из файла/потока, обрабатывает их, записывает обратно в файл/поток.

Rubynovich, 30 мая 2006

Как японская письменность повлияла на язык Ruby

Вы в курсе того, как пишут в Японии? В Японии пишут так:

  • существует принятая последовательность написания каждого иероглифа. Без вариантов;
  • иероглифы в качестве средства японской письменности были заимствованы из Китая;
  • решающее значение для развития письменности в Японии имело распространение буддизма в стране;
  • восприняв иероглифиеское письмо, японцы не могли вначале отделить его от китайского языка;
  • писцы разных буддийских храмов стали придумывать системы условных значков, при помощи которых китайский текст можно было читать по-японски;
  • Кукаю часто приписывают создание одной из систем японского слогового письма — хираганы;
  • созданию фонетического письма способствовали два обстоятельства:
  1. каждый китайский иероглиф обозначает слог, в большинстве случаев равный слову и может быть использован чисто фонетически без связи со смыслом;
  2. знакомство японцев с санскритом подсказывало им возможность создания фонетического письма.
  • одновременно с хираганой, вошла в употребление катакана — система слогового письма, знаки которой содержали не более трех черт и были получены в результате упрощения уставной формы иероглифов;
  • сфера катаканы была такая же, как и у хираганы. Каждый алфавит состоит из 46 силлабем;
  • начались контакты Японии с европейскими странами. Для записи иностранной лексики стала использоваться катакана;
  • в специфических целях была создана (в двух вариантах) латинская азбука для записи звуков японской речи;
  • китайские иероглифы, азбуки катакана и хирагана, латинские азбуки (ромадзи) и составляют современную систему письма;

Подведем небольшой итог: в Японии используются пять видов письма. Это чертыре азбуки и иероглифы. Каждая азбука — слоговая и в ней по 48 знаков. Иероглифов намного больше.

Идем дальше:

  • концепции всех каллиграфических стилей Японии строились на представлении о том, что написанный текст должен доставлять эстетическое удовольствие;
  • из 25 тысяч иероглифов, 80-90% составляют лексиконное достояние;
  • одна только точка в структуре иероглифа имеет 24 разновидности;
  • карандаш, стальное перо или мел, при всей их пригодности, никогда не дадут истинного представления о внутренних и художественных качествах иероглифов и знаков японской азбуки;
  • в Японии немало сторонников полного перехода на ромадзи (латиницу), но практических перспектив осуществления этих планов в ближайшем будущем не предвидится;
  • каждая азбука имеет свое уникальное назначение:
  • катакана используется в:
  1. тексте японских телеграмм;
  2. законах, распоряжениях и прочих официальных документах;
  3. транскрипции иностранных имен собственных;
  • хирагана используется в:
  1. надписях вдоль железных дорог;
  • ромадзи используется в:
  1. международных телеграммах на японском языке;
  • китайские иероглифы используются во всех остальных случаях.

Ну как? Вот на основе этой справочной информации. Смысл всего вышесказанного вот в чем:

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

Как известно, Ruby — язык программирования японского происхождения. На нем японская письменность отразилась следующим образом:

  • язык получился мультипарадигменным, то есть возможно написание программ в разных стилях;
  • очень много специальных методов на все случаи жизни 80-90% из которых написаны пользователями языка;
  • программирование в стиле Си воспринимается как нечто чужеродное, но доминирования какого-либо одного стиля нет;
  • в процессе написания программы очень много внимания уделяется эстетичности письма (красоте программного кода).

Rubynovich, 30 мая 2006

Можно ли обойтись без условных операторов (в их нынешнем виде)?

Во времена учебы в институте (ГОУ МГИУ), произошла со мной одна история. По предмету "Метрология программного обеспечения" нам выдали темы курсовых работ. Мне попалась работа, суть которой заключалась в подсчёте числа условий внутри условного оператора if. Программу для курсового я писал на языке программирования Ruby (на тот момент его только планировали преподавать на первых курсах). Вполне естественно, что программа была написана "в две строчки" и при помощи регулярных выражений (приводить ее не буду: ничего интересного в ней нет). В качестве проверки, преподаватель применял программу для измерения метрики на саму себя. Каково же было его удивление, когда он увидел метрику равную нулю — сама программа не содержала условного оператора if (использовалось всего два метода scan и size). Вполне естественно, что он начал возмущаться и говорить, что "столь сложную программу написать без единого условия невозможно". Его удалось переубедить, но ценой нескольких миллионов нервных клеток.

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

Для начала, немного теории. В Ruby (а также в Perl) существует метод <=>, широко применяемый в различных итераторах (sort, max, min и так далее.). По сути, он включает в себя всевозможные операции сравнения (<,>,>=,<=,== и !=) и может замечательно их заменять. Работа этого метода заключается в том, что он возвращает целое число (-1,0 или 1), в зависимости от результата сравнения. Пример:

3 <=> 5 #-> -1
5 <=> 3 #-> 1
3 <=> 3 #-> 0
5 <=> 5 #-> 0

Как видно из примера, в случае, если левый агрумент меньше правого, то метод <=> возвращает -1, если больше, то 1, а если они равны, то 0. Именно это свойство метода <=> я и собираюсь использовать.

Но для "эквивалентной замены" условного оператора этого недостаточно. Требуется что-то еще... Это что-то называется "возможность отрицательной индексации в массивах", то есть в Ruby массивы обладают возможностью индексировать свои элементы не только с 0 до N-1, но и с -N до -1, где N — размер массива. Последний элемент будет иметь индекс -1, предпоследний -2 и так далее. Причем, отрицательная индексация уже включена и для ее применения никаких дополнительных действий не требуется. Пример:

maccuB = [1,2,3,4,5]
maccuB[0]  #-> 1
maccuB[-5] #-> 1
maccuB[-1] #-> 5
maccuB[4]  #-> 5

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

maccuB = [1,2,3,4,5]
maccuB[0]  #-> 1
maccuB[maccuB.size - 5] #-> 1
maccuB[maccuB.size - 1] #-> 5
maccuB[4]  #-> 5

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

Применим описанные выше свойства для демонстрации "альтернативного условного оператора" (сокращенно АУО). Для наглядности, решим задачку: "Даны два числа. Вывести слово 'больше', если первое число больше второго, 'меньше', если оно меньше. В случае равенства этих чисел, нужно вывести слово 'равны'." Решение:

nepBoe_4ucJIo, BTopoe_4ucJIo = 4,5
puts ['равны','больше','меньше'][nepBoe_4ucJIo <=> BTopoe_4ucJIo]

Вполне естественно, что задача сугубо специфическая и придумана специально для пояснения сути АУО, но изящество, с которым она решена, позволяет об этом временно забыть. =)

А что будет, если надо использовать не три варианта, а меньше? Хм. Вопрос вполне уместный. Давайте попытаем задачу: "Даны два числа. Вывести слово 'равны', если они равны и словно 'неравны', иначе." Возможное решение:

nepBoe_4ucJIo, BTopoe_4ucJIo = 4,5
puts ['равны','неравны','неравны'][nepBoe_4ucJIo <=> BTopoe_4ucJIo]

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

nepBoe_4ucJIo, BTopoe_4ucJIo = 4,5
puts ['равны','неравны'][nepBoe_4ucJIo <=> BTopoe_4ucJIo]

Красиво? Вот и я так думаю. Давайте посмотрим, как это получилось. Про элемент 'равны' вопросов возникать не должно. Но как быть со вторым вариантом? Дело в том, что второй вариант имеет два индекса 1 и -1, которым как раз и соответствует неравенство. Справедливости ради, заметим, что элемент 'равны' также имеет два индекса (0 и -2), но метод <=> значения -2 не принимает. Вполне естественно, что использование АУО дает пищу для ума и ставит перед исследователями и программистами множество вопросов. Я сам уже начал подобные исследования и у меня есть несколько ответов и разработана методика их получения, но их описание требует времени, а его как всегда не хватает.

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

Rubynovich, 31 мая 2006

Какой язык программирования учить первым?

Читайте также статью «Языки программирования в школе» из журнала «Потенциал.

Прочитал переписку Роганова с Сузи и у меня возник вопрос: если бы моим первым языком программирования был бы Ruby, то смог бы я изучить С, C++, Java, Pascal?

Не обязательно конечно Ruby... можно Python. Дело в том, что стиль программирования на этих языках разнится со стилем программирования на остальных "попсовых" языках программирования. Допустим, что мне сегодня не очень хочется программировать на Си, т.к. любую требуемую мне задачу я могу решить на Ruby. Заставить меня уже преподаватели не в силах (в этом году пишу диплом на Ruby). Получается, что все преподносят язык Ruby как первый, а на самом деле он является последним. Остальные языки являются как бы приложениями, которые служат для расширения библиотеки языка (Perl приносит регулярные выражения, Python — свою библиотеку и ранее разработанные программы и модули, Cи используется для написания критичных участков кода). Переучиваться программировать на той же Java ох как трудно (до сих пор не могу себя заставить писать на ней курсовые).

Полная зависимость от Ruby пришла ко мне, когда я стал преподавать. Честно говоря, мои программы и моих студентов, на тот момент, мало, чем отличались. Может, именно поэтому я согласился преподавать... но только на Ruby, т.к. этот язык уже на тот момент меня восхищал. Я преподавал Ruby совместно с учителями, которые преподавали Python и Си. На текущий момент я хочу сказать, что студенты, которые до Ruby писали программы на Си, на него уже больше не возвращаются (я говорю про студентов, которые учатся). Студенты, которые писали до Ruby на Python, пишут программы на Python, а потом доставляют 'end'-ы и вместо ':' ставят 'do' и 'then'.

Программирование в стиле Ruby (через итераторы и не жалея памяти) приходит к ним очень туго. Хотя, казалось бы, почти одинаковые языки. Отсюда следует, что Python следует изучать после Ruby (как это сделал я). Тогда видна вся "недоделанность" Питона.

Итог: изучение Ruby "опасно" тем, что вы его конкретно полюбите, а полюбя сделаете все для его развития в России, а у меня в этом прямая заинтересованность! Вообще, к выбору языка программирования подхожу просто. Существование языка не должно зависеть от какой-либо коммерческой компании, быть свободным, или, на худой конец, открытым ПО. Это гарантирует развитие языка, даже после смерти разработчика (Боже, храни Маца). Исходя из этого предположения, к "смертникам" относятся: Java, Object Pascal, он же Delphi, а про Perl и Python вопрос открытый (поправьте меня в комментариях). Причем изучение языков и изучение новых технологий — разные вещи. Изучать новые технологии стоит всегда! Если технология умрет, не беда — появятся другие. Изучение технологии дает знания и опыт (главное, чтобы ваш язык программирования эту технологию поддерживал). А опыт? в нашем нелегком деле? ценее сертификатов... В общем, Ruby поддерживает все интересующие меня технологии (оконные интерфейсы, .Net и DRb aka CORBA aka COM). Именно поэтому я и выбираю его. И чтобы совсем уже ответить на поставленный вопрос: Ruby стал бы моим последним языком программирования, но для общего развития я бы стал изучать устаревшие Perl, Python, Java (что я и сделал). Rubynovich 17:42, 9 июня 2006 (UTC)


У меня конечно академического опыта нет, а подход строго практический - но по моему опыту чтобы понять, немного "почуствовать" Ruby и осознать его прелесть нужно сначала поработать с более строгими или архаичными яыками (пусть даже скриптовыми).--Julik 00:21, 12 июня 2006 (UTC)

Условные операторы нового поколения (контракты)

Возможно, кому-то уже приходила в голову идея подобного рода. А что если создать такой механизм при котором можно было бы с легкостью добавлять условие к условному оператору (типа case или if)? Или динамически его удалять... Интересно? Мне тоже! Давайте немного поразмышляем.

Что такое условный оператор? По сути, это неупорядоченный набор структур вида: условие => действие. Ничего не напоминает? Вроде как на хеш похоже. Давайте думать дальше. Ключ у хеша может быть любым. Точно также, как и условие. А что может выполнять роль действия? Метод или лямбда-функция (в народе "проки", от англ. proc). Заметим, что метод должен быть объектом класса Method, т.е. имя должно быть преобразовано методом method. Например, вот так:

method( :meTog )

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

Теперь надо как-то назвать эту структуру. После недолгих раздумий, было решено именовать ее Contract и построить ее на базе класса Hash, добавив к нему всего два метода: .add_contract и .find_contracts. Вот их реализация и коротенький пример:

class Hash
    def add_contract(key, &block)
        self[ key ] = block
    end

    def find_contracts
        find_all{ |key,value|
            yield key
        }.map{ |key,value|
            value
        }
    end   
end

hash = {}
hash.add_contract( 5 ){ |i| puts i }
p hash.find_contracts{ |i| i == 5 }     #-> [#<Proc:0x02777bf8@test019.rbw:40>]

Как видно, результатом является массив "проков", где для обработки каждого "прока" надо вызвать метод .call. Rubynovich 19:07, 9 июня 2006 (UTC)




Контроль типов через расширение класса

Очень часто приходится слышать, что "Ruby — нетипизирован". Это очень распространенное заблуждение основано на том, что все переменные в Ruby имеют одинаковый тип: Object. По существу, это означает отключение контроля типов, а не отсутствие типизации. Ruby, по прежнему, остается строготипизированным языком программирования. В виду засилия различных Си-подобных языков программирования, у многих программистов укоренился в сознании тот факт, что типизация переменных — неотъемлемая часть типизации вообще. Догадаться о том, что именно значение, а не переменная определяют тип, многим не судьба. К тому же, многие даже не догадываются о том, что программа может быть написана без переменных вообще.

Вот именно отсутствие контроля за типом переменной и открывает перед программистом богатые возможности. Ведь не нужно плодить шаблоны[2], достаточно написать абстрактный код с переменными, значение и тип которых будут понятны позже. Ведь, программист с самого начала мало представляет облик будущей программы. Это лишь с опытом приходит способность моделировать полностью фукциональный код еще до его написания. А по молодости лет приходится пробовать и ошибаться. Необходимость же контролировать еще и типы переменных, вносит еще большую неразбериху в процесс программирования. Устранив обязательный контроль типов переменных можно существенно упростить жизнь программисту и повысить скорость написания кода.

В Ruby можно определить тип получаемых данных по методам, которые были вызваны для получения этих данных. Например, метод .join со 100% вероятностью формирует объект класса String (строка), а методы .scan и .split — объект типа Array (массив). Но это не всегда работает, так как есть методы, которые есть у нескольких классов и, в зависимости от класса, они возвращают объект того или иного класса. Не могу привести пример таких методов, но вполне допускаю, что они могут быть[3]. Контроль за классом объекта, через вызываемые от него методы, в Ruby называется "Утиным контролем типов". "Утиный" потому, что если объект "крякает как утка, то он и является уткой!". Иными словами, если методы, которые мы хотим вызвать, успешно вызвались, то класс исходного объекта не важен. Остается подобрать такую последовательность методов, которая дает верный результат. От том, насколько просто или сложно это реализовать — вопрос отдельный. Могу лишь поделиться своим наблюдением, что первым в последовательности методов ставят метод-адаптер, который конвертирует объект в необходимый для второго метода класс. А дальше все проходит по заведомо определенному маршруту преобразований.

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

def meTog( napameTp_1 )
  raise "Не тот тип входных данных!" if [String, Array].include?( napameTp_1.class )
  case napameTp_1.class
    when String:
       napameTp.split()
    when Array:
       napameTp
  end.map{ |s| s.to_i }
end
Информация

Метод .class возвращает объект класса Class, то есть код o6bekT.class.class == Class будет всегда возвращать true

Обратите внимание на то, что napameTp_1 может принадлежать к двум разным классам: String и Array. Это как раз то самое, преимущество, которое появилось благодаря отмене обязательного контроля типов переменных. Во время обязательного конроля за типом переменной мы обязаны указывать ее тип (и только один). Уж позже придумали плодить иерархию классов лишь для того, чтобы иметь возможность точно такого же контроля.

Но все равно, предыдущий код "кривой", так как придется писать отдельный код (внутри метода) для обработки данных класса String и Array. А чем меньше[4] метод, тем лучше. Правильней было бы использовать такую возможность Ruby, как расширение класса.

Информация

Расширение класса — это механизм, который позволяет динамически добавить или переопределить метод. Это новое ООП-понятие, которое используется в динамических ООП-языках вместо полиморфизма

Давайте реализуем предыдущий код, но с использованием расширения класса.

class String
  def meTog
    split().meTog
  end
end

class Array
  def meTog
    map{ |s| s.to_i }
  end
end

napameTp_1.meTog

В зависимости от класса, будет вызван либо один meTog, либо другой. При этом, meTog в классе String используется как адаптер для meTog'а класса Array. Единственный недостаток такого подхода — случайное переопределение базовых методов. Но эта случайность быстро отлавливается. Rubynovich 17:40, 5 ноября 2006 (UTC)

См. также

В словарике философствующего информатика:


^  — Cуществование шаблонов в языках C++ и C# наглядно показывает, что профессиональным программистам мешает излишне строгий контроль типов.

^  — Подробное исследование мной пока не проводилось, но в ближайшее время оно будет проведено и опубликовано отдельной статьей.

^  — Имеется в виду размер кода.