Статьи

Построение римских цифр за день с помощью метапрограммирования Ruby

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

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

В этом руководстве мы работаем на основе того, что на вашем компьютере установлен Ruby.

От IRB

Самое быстрое место, где можно узнать о любой новой концепции в Ruby, — это ввести ее в терминал IRB. В окне терминала введите следующее:

$ irb 

Теперь вы должны быть в терминале IRB. Далее мы собираемся добавить метод к классу строк с именем William. Этот метод просто возвращает мое имя, но он должен дать вам представление о том, что такое динамический метод.

 class String def william return "William" end end 

Теперь в терминале IRB мы можем сделать следующее:

 irb> "string".william => "William" 

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

Решение проблемы римских цифр с использованием динамических методов

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

Как бы мы написали компьютерную программу, которая преобразует число в римскую цифру, используя динамические методы?

Давайте посмотрим на одно возможное решение, которое достигает 5000. Из окна терминала, давайте создадим новый проект

 mkdir roman_numeral 

Теперь мы можем создать новый файл:

 touch roman_numerals.rb 

Мне нравится писать тесты для моего кода, поэтому мы установим RSpec и создадим спецификацию:

 gem install rspec 

После того, как мы установили гем RSpec, мы можем инициализировать Rspec

 rspec --init 

В нашем каталоге спецификаций мы можем создать спецификацию римской цифры.

 touch spec/roman_numeral_spec.rb 

Теперь у нас есть все готовое для начала проекта. Давайте начнем с написания множества тестов. Добавьте следующее в наш spec / roman_numeral_spec.rb :

 require_relative "../roman_numerals.rb" RSpec.describe Numeric, "#roman_numeral" do context "#convert number into roman numeral" do it "1.roman_numeral returns I" do expect(1.roman_numeral).to eq "I" end it "5.roman_numeral returns I" do expect(5.roman_numeral).to eq "V" end it "4.roman_numeral returns IV" do expect(4.roman_numeral).to eq "IV" end it "6.roman_numeral returns IV" do expect(6.roman_numeral).to eq "VI" end it "7.roman_numeral returns VII" do expect(7.roman_numeral).to eq "VII" end it "8.roman_numeral returns VII" do expect(8.roman_numeral).to eq "VIII" end it "9.roman_numeral returns IX" do expect(9.roman_numeral).to eq "IX" end it "10.roman_numeral returns IX" do expect(10.roman_numeral).to eq "X" end it "13.roman_numeral returns XIII" do expect(13.roman_numeral).to eq "XIII" end it "15.roman_numeral returns XV" do expect(15.roman_numeral).to eq "XV" end it "18.roman_numeral returns XVIII" do expect(18.roman_numeral).to eq "XVIII" end it "19.roman_numeral returns XIX" do expect(19.roman_numeral).to eq "XIX" end it "30.roman_numeral returns XXX" do expect(30.roman_numeral).to eq "XXX" end it "50.roman_numeral returns L" do expect(50.roman_numeral).to eq "L" end it "51.roman_numeral returns LI" do expect(51.roman_numeral).to eq "LI" end it "89.roman_numeral returns LXXXIX" do expect(89.roman_numeral).to eq "LXXXIX" end it "99.roman_numeral returns XCIX" do expect(99.roman_numeral).to eq "XCIX" end it "145.roman_numeral returns CXLV" do expect(145.roman_numeral).to eq "CXLV" end it "459.roman_numeral returns CDLIX" do expect(459.roman_numeral).to eq "CDLIX" end it "1984.roman_numeral returns MCMLXXXIV" do expect(1984.roman_numeral).to eq "MCMLXXXIV" end it "1545.roman_numeral returns MDXLV" do expect(1545.roman_numeral).to eq "MDXLV" end it "4936.roman_numeral returns MMMMCMXXXVI" do expect(4936.roman_numeral).to eq "MMMMCMXXXVI" end end end 

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

 it "1984.roman_numeral returns MCMLXXXIV" do expect(1984.roman_numeral).to eq "MCMLXXXIV" end 

Динамические методы позволяют нам создавать метод, который мы можем вызывать в экземплярах класса Fixnum . В Ruby есть полезные методы, такие как to_s который преобразует число в строку. В этом случае мы вдохновляемся соглашениями Ruby и добавляем метод без изменения какой-либо из библиотек Ruby. Мы просто добавляем новый метод. Посмотрим, как это делается.

Первое, что мы делаем, это определяем класс Fixnum. Даже если уже существует класс Ruby с именем Fixnum, выполнение следующих действий не приведет к перезаписи реального класса. В нашем файле roman_numerals.rb :

 class Fixnum end 

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

 class Fixnum def roman_numeral return "The Romans have no zeros just heros. https://www.theguardian.com/notesandqueries/query/0,5753,-1358,00.html " if self == 0 symbols = {1000 => "M",900 => "CM", 500 => "D",400 => "CD", 100 => "C",90 => "XC", 50 => "L",40 =>"XL", 10 => "X",9 => "IX", 5 => "V",4 => "IV", 1=> "I"} multiplier = self symbol = [] count = 0 symbols.each do |num, sym| symbol.push(sym * (multiplier/num)) multiplier = multiplier % num count += 1 end return symbol.join end end 

Когда вы запустите RSpec, вы должны увидеть, что все тесты пройдены. Из текущего каталога запустите следующее:

 rspec 

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

ВНИМАНИЕ: перезапись существующих методов

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

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

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

Вывод

Теперь, когда вы быстро познакомились с аспектом Ruby MetaProgramming, как бы вы его использовали? Как вы думаете, динамические методы полезны или они слишком опасны для собственного блага? Я хотел бы услышать ваше мнение в комментариях ниже.