Ruby/Идеология

Материал из Викиучебника — открытых книг для открытого мира
Перейти к: навигация, поиск

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

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

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

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

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

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 "n=#{n}" # m - локальная переменная внутри блока, вне блока ее не существует

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

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

Лично я, в своей практике, хочу преподавать студентам циклы в контексте устаревших конструкций (наряду с 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 и так далее. Причем, отрицательная индексация уже включена и для ее применения никаких дополнительных действий не требуется. Пример:

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

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

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

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

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

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

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

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

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

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

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

Красиво? Вот и я так думаю. Давайте посмотрим, как это получилось. Про элемент 'равны' вопросов возникать не должно. Но как быть со вторым вариантом? Дело в том, что второй вариант имеет два индекса 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 [<u>String</u>, <u>Array</u>].include?( napameTp_1<u>.class</u> )
   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('')<u>.meTog</u>
   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)

Что дает высокоуровневый подход?[править]

Все началось с заметки на http://bash.org.ru:

Vixen: 54308428790203478762340052723346983453487023489987231275412390872348475

tosh: что это за безумие?

Vixen: это?

Vixen: это пятьдесят четыре довигинтиллиона триста восемь унвигинтиллионов четыреста двадцать восемь вигинтиллионов семьсот девяносто новемдециллионов двести три октодециллиона четыреста семьдесят восемь септендециллионов семьсот шестьдесят два сексдециллиона триста сорок квиндециллионов пятьдесят два кваттуордециллиона семьсот двадцать три тредециллиона триста сорок шесть дуодециллионов девятьсот восемьдесят три ундециллиона четыреста пятьдесят три дециллиона четыреста восемьдесят семь нониллионов двадцать три октиллиона четыреста восемьдесят девять септиллионов девятьсот восемьдесят семь секстиллионов двести тридцать один квинтиллион двести семьдесят пять квадриллионов четыреста двенадцать триллионов триста девяносто миллиардов восемьсот семьдесят два миллиона триста сорок восемь тысяч четыреста семьдесят пять

Тут же появилась идея реализовать программу, которой потенциально мог воспользоваться Vixen. Первым прислал решение Руслан Мухаметов:

#!/usr/bin/ruby
# Программа для написания чисел на русском языке из цифрового представления
# Версия 0.1.1
#
# История изменений
# 0.1.1
#   * Убраны лишние пробелы в числах с 00000
#
# Email, JabberID: ru.ruslan@gmail.com
#
# Названия больших чисел взяты из статьи http://archive.is/20121225033145/mirozdanie.narod.ru/Knowleg.html и могут отличаться от иных источников
#
# Пример использования: 
# $ruby bignums.rb 54308428790203478762340052723346983453487023489987231275412390872348475
# пятьдесят четыре дуовигинтиллиона ......... тысяч четыреста семьдесят пять

nums_for_0_9 = %w{ноль один два три четыре пять шесть семь восемь девять}
nums_for_10_19 = %w{десять одиннадцать двенадцать тринадцать четырнадцать пятнадцать шестнадцать семнадцать восемнадцать девятнадцать}
nums_for_10_90 = %w{десять двадцать тридцать сорок пятьдесят шестьдесят семьдесят восемьдесят девяносто}
nums_for_100_900 = %w{сто двести триста четыреста пятьсот шестьсот семьсот восемьсот девятьсот}

nums = []
nums << nums_for_0_9
nums.flatten!
nums[0] = ""
nums << nums_for_10_19
nums.flatten!
for i in 2..9 do
  nums << nums_for_10_90[i-1]
  for j in 1..9 do
    nums << nums_for_10_90[i-1] + " " + nums[j]
  end
end

for i in 1..9 do
  c = nums_for_100_900[i-1]
  nums << c
  for j in 1..99 do
    nums << c + " " + nums[j]
  end
end

die = proc{ |msg| puts msg; exit; }

x = ARGV[0] or die.call "Usage: ./bignums.rb NUMBER"

threes = x.reverse.scan(/\d{1,3}/).map{|x| x.reverse}

rod_mult = proc{ |arr|
  ans = [arr[2],arr[0],arr[1..1]*3,arr[2..2]*5].flatten
}

ok_m = rod_mult.call ["","а","ов"]
ok_z = rod_mult.call ["а","и",""]
threes_names = %w{тысяч миллион миллиард триллион квадриллион квинтиллион секстиллион септиллион
  октиллион нониллион дециллион андециллион дуодециллион тредециллион кваттордециллион квиндециллион сексдециллион септемдециллион
  октодециллион новемдециллион вигинтиллион анвигинтиллион дуовигинтиллион тревигинтиллион кватторвигинтиллион квинвигинтиллион 
  сексвигинтиллион септемвигинтиллион октовигинтиллион новемвигинтиллион тригинтиллион антригинтиллион}
  


i = -1
ans = threes.map do |s|
  i+= 1
  n = s.to_i
  next if n == 0
  if i == 0
    nums[n]
  elsif i == 1
    # тысячи
    e = n % 10
    d = n % 100
    suff = ok_z[e]
    suff = ok_z[0] if d > 10 and d < 20
    t = nums[n]
    if e == 1 or e == 2 and not (d > 10 and d < 20)
      # один и два перед тысячами меняются.
      # не регексп, чтобы работал и юникод и однобайтные кодировки
      t = " " + t + " "
      t = t.sub(" один "," одна ").sub(" два "," две ")
      t = t[1..99].chop
    end
    t + (n!=0 ? " " : "") + threes_names[i-1] + suff
  else
    e = n % 10
    d = n % 100
    suff = ok_m[e]
    suff = ok_m[0] if d > 10 and d < 20
    nums[n] + (n!=0 ? " " : "") + threes_names[i-1] + suff
  end
end.reverse.compact.join(" ")

puts ans

Прочитав этот код я ничего не понял. А самый верный способ понять код — провести рефакторинг. Что я и решил сделать:

#!/usr/bin/ruby
# Программа для написания чисел на русском языке из цифрового представления
# Версия 0.2
#
# История изменений
# 0.2
#   * Проведен глубокий рефакторинг кода. Теперь решение в "одну строчку"
# 0.1.1
#   * Убраны лишние пробелы в числах с 00000
#
# Email, JabberID: ru.ruslan@gmail.com
# Email, JabberID: rubynovich@gmail.com
#
# Названия больших чисел взяты из статьи http://archive.is/20121225033145/mirozdanie.narod.ru/Knowleg.html и могут отличаться от иных источников
#
# Пример использования:
#   $ruby bignums.rb 54308428790203478762340052723346983453487023489987231275412390872348475
#   => пятьдесят четыре дуовигинтиллиона ......... тысяч четыреста семьдесят пять

nums_for_1_9 = %w{один два три четыре пять шесть семь восемь девять}
nums_for_10_19 = %w{десять одиннадцать двенадцать тринадцать четырнадцать пятнадцать шестнадцать семнадцать восемнадцать девятнадцать}
nums_for_20_90 = %w{двадцать тридцать сорок пятьдесят шестьдесят семьдесят восемьдесят девяносто}
nums_for_100_900 = %w{сто двести триста четыреста пятьсот шестьсот семьсот восемьсот девятьсот}

nums = nums_for_1_9 + nums_for_10_19

nums += nums_for_20_90.zip([nums_for_1_9]*nums_for_20_90.size).map{ |decade,array|
  array.map{ |num| [decade,num].join(" ") }.unshift( decade )
}.flatten

nums += nums_for_100_900.zip([nums]*nums_for_100_900.size).map{ |hundred,array|
  array.map{ |num| [hundred,num].join(" ") }.unshift( hundred )
}.flatten

nums.unshift("")

ok = { 1 => ["", "а", "и", "и", "и", "", "", "", "", ""] }
ok.default = ["ов", "", "а", "а", "а", "ов", "ов", "ов", "ов", "ов"]

threes_names = %w{тысяч миллион миллиард триллион квадриллион квинтиллион секстиллион септиллион
  октиллион нониллион дециллион андециллион дуодециллион тредециллион кваттордециллион квиндециллион сексдециллион септемдециллион
  октодециллион новемдециллион вигинтиллион анвигинтиллион дуовигинтиллион тревигинтиллион кватторвигинтиллион квинвигинтиллион
  сексвигинтиллион септемвигинтиллион октовигинтиллион новемвигинтиллион тригинтиллион антригинтиллион}

threes = ARGV[0].reverse.scan(/\d{1,3}/).map{|x| x.reverse} rescue raise("Usage: ./#{$0} NUMBER")
  
puts (1...threes.size).map{ |i|
  threes[i].to_i.instance_eval{ |n|
    if [1,2].include?(n % 10)&&!(11...20).include?(n % 100)&&(i==1)
      nums[n].sub("один","одна").sub("два","две")
    else
      nums[n]
    end + " " + threes_names[i-1] + ok[i][ (11...20).include?(n % 100) ? 0 : n % 10 ] if n.nonzero?
  }
}.compact.reverse.push(nums[threes[0].to_i]).join(" ")

Сейчас пока некогда рассказывать как это получилось, но чуть позже опишу свои действия по шагам. Rubynovich

Еще один вариант решения:

#!/usr/bin/ruby
# Программа для написания чисел на русском языке из цифрового представления
# Версия 0.3
#
# История изменений
# 0.3
#   * Проведен глубокий рефакторинг кода. Немного изменена концепция программы. 
#   * Основной процесс записи названия чисел в массив (r) выполнен с помощью внутреннего итератора (each_with_index) и занимает всего одну строчку. 
#   * Процесс добавления названия чисел от 1 до 999 в массив (nums) также выполнен другим образом.
# 0.2
#   * Проведен глубокий рефакторинг кода. Теперь решение в "одну строчку"
# 0.1.1
#   * Убраны лишние пробелы в числах с 00000
#
# Email, JabberID: ru.ruslan@gmail.com
# Email, JabberID: rubynovich@gmail.com
# Email: dimak33@yandex.com
#
# Названия больших чисел взяты из статьи http://archive.is/20121225033145/mirozdanie.narod.ru/Knowleg.html и могут отличаться от иных источников
#
# Пример использования:
#   $ruby bignums.rb 54308428790203478762340052723346983453487023489987231275412390872348475
#   => пятьдесят четыре дуовигинтиллиона ......... тысяч четыреста семьдесят пять

nums = %w{один два три четыре пять шесть семь восемь девять}
nums_for_10_19 = %w{десять одиннадцать двенадцать тринадцать четырнадцать пятнадцать шестнадцать семнадцать восемнадцать девятнадцать}
nums_for_20_90 = %w{двадцать тридцать сорок пятьдесят шестьдесят семьдесят восемьдесят девяносто}
nums_for_100_900 = %w{сто двести триста четыреста пятьсот шестьсот семьсот восемьсот девятьсот}

nums+=nums_for_10_19

[nums_for_20_90,nums_for_100_900].each_with_index do |n,index|
n.each do |x|
nums<<x
(90*index+9).times {|i| nums<<x+' '+nums[i]} 
end
end

nums.push('')

ok = { 1 => ["", "а", "и", "и", "и", "", "", "", "", ""] }
ok.default = ["ов", "", "а", "а", "а", "ов", "ов", "ов", "ов", "ов"]

threes_names = %w{тысяч миллион миллиард триллион квадриллион квинтиллион секстиллион септиллион
  октиллион нониллион дециллион андециллион дуодециллион тредециллион кваттордециллион квиндециллион сексдециллион септемдециллион
  октодециллион новемдециллион вигинтиллион анвигинтиллион дуовигинтиллион тревигинтиллион кватторвигинтиллион квинвигинтиллион
  сексвигинтиллион септемвигинтиллион октовигинтиллион новемвигинтиллион тригинтиллион антригинтиллион}.unshift('')

threes = ARGV[0].gsub(/(\d)(?=(\d{3})+$)/, '\1 ').split.reverse rescue raise("Usage: ./#{$0} NUMBER")

r=[]
  
threes.each_with_index{|x,i| x.to_i>0 ? r<<[nums[x.to_i-1],threes_names[i]+ok[i][(11...20)===(x.to_i%100) ? 0 : x.to_i%10]] : r<<[nums[x.to_i-1]]}

p r.reverse.join(' ').sub(/один тысяча/, 'одна тысяча').sub(/два тысячи/, 'две тысячи').gsub(/\s+/,' ').chop

См. также[править]

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


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

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

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