Статьи

Ruby, Python, Java, C и счастье программиста

«Ruby создан для того, чтобы сделать программистов счастливыми». Юкихиро «Мац» Мацумото

Не все могут с этим согласиться, но, как Rubyist, я думаю, что Матц достиг своей цели дизайна. В синтаксисе Ruby есть что-то нематериальное, что делает его веселым, полезным и простым в использовании — что-то, что делает меня счастливым. Я подумал, что было бы интересно сравнить Ruby с несколькими другими языками, посмотрев, как разные разработчики с открытым исходным кодом реализовали один и тот же метод или функцию на каждом языке. Чем отличаются языки? Они делают тебя одинаково счастливым?

И что может быть лучшим примером, чем внутри самого Ruby! Сегодня я собираюсь посмотреть, как метод Ruby Hash # fetch реализован в Ruby (Rubinius), Python (Topaz), Java (JRuby) и, наконец, в C (по стандарту Ruby 2.0). Конечно, есть много других языков программирования, даже других версий Ruby, но рассмотрение небольшого фрагмента внутренних компонентов Ruby дает нам интересный пример и позволяет сравнивать яблоки с яблоками.

Hash # выборки

Для тех из вас, кто не знаком с Ruby или методом Hash # fetch, давайте сначала рассмотрим, что делает Hash # fetch. Извлечение позволяет вам искать значение из хеша, используя ключ, как это делает метод []. Кроме того, fetch также позволяет вам указать значение по умолчанию, которое должен возвращать Ruby, если не может найти запрошенный ключ. Вот пример из документации по Ruby:

fetch1

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

fetch2

Hash # fetch в Rubinius

Давайте начнем наш обзор с рассмотрения того, как Rubinius реализует Hash # fetch. Поскольку Rubinius использует Ruby для реализации своего ядра, чтение исходного кода Rubinius — отличный способ точно понять, что делает данный метод Ruby.

Вот реализация Rubinius для Hash # fetch из kernel / common / hash19.rb:

RBX
Как вы можете видеть здесь, есть много критики в синтаксисе Ruby. И, конечно, это не самый элегантный пример кода Ruby в мире.

Но мне нравится это. Это делает меня счастливым. Почему? Потому что просто понять, что делает этот код. Язык Ruby не мешает значению кода, тому, что код пытается достичь.

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

Hash # fetch в топазе

Одним из наиболее интересных событий в этом году в сообществе Ruby стало сообщение о том, что Алекс Гейнор внедрил Ruby с использованием Python и инструментария PyPy. PyPy позволяет разработчикам Python реализовывать компилятор и виртуальную машину для своего собственного языка, используя подмножество Python под названием «RPython». PyPy преобразует пользовательскую виртуальную машину разработчика в C с помощью сложной серии оптимизаций, а затем компилирует C в быструю собственную машину язык.

Давайте посмотрим, как выглядит тот же код в Python:

топаз

Мне Python действительно кажется родственным языком для Ruby. Помимо незначительных различий между пробелами, двоеточиями и конечными ключевыми словами, эти два языка очень похожи.

Одно важное различие между ними заключается в том, что Python не поддерживает замыкания так же элегантно, как Ruby с блоками. Параметр блока и вызов invoke_block здесь являются частью реализации блоков Ruby для Topaz.

Читать этот код почти так же легко, как и читать реализацию Rubinius. Любой дополнительный код или многословность здесь просто из-за деталей того, как Topaz работает внутри. Я думаю, что я мог бы стать счастливым, используя Python, если бы потратил некоторое время на изучение использования языка — по крайней мере, если бы мой редактор правильно обрабатывал пробелы!

Hash # fetch в JRuby

Сходство между Ruby и Python становится более очевидным, когда вы сравниваете любой из них с Java. Чтобы понять, что я имею в виду, взгляните на реализацию Hub # fetch на JRuby:

JRuby

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

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

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

Hash # fetch в C

Теперь давайте посмотрим на официальную версию Hash # fetch. Вот код C, который фактически использует ваша программа Ruby, если вы используете Ruby 2.0:

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

Если вы знакомы с идиоматическим стилем исходного кода Ruby, с такими конструкциями, как «RHASH», «st_lookup» и «rb_scan_args», то это не так уж и сложно. Но здесь все еще есть очень запутанные детали, такие как использование rb_protect для обработки исключений, которые могут возникнуть при генерации сообщения об ошибке «ключ не найден».

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

Недобросовестные сравнения

Конечно, все эти языки очень разные, предназначены для разных целей. C — это действительно сокращение для написания ассемблера, и, при правильном использовании, дает вам огромный контроль, гибкость и скорость. Java позволяет вам писать элегантный, чистый и объектно-ориентированный код, не беспокоясь о деталях оборудования или проблемах переносимости. И это все еще работает довольно быстро на JVM. JRuby, по сути, является одной из самых быстрых версий Ruby, в некоторых случаях быстрее, чем MRI Ruby.

Моя причина для сравнения этих языков друг с другом состоит в том, чтобы напомнить вам о том, как вы радуетесь написанию Ruby. Не принимайте это как должное! За последние 20 лет Matz и основная команда Ruby проделали огромную работу, чтобы принести счастье программисту.

И почему другие талантливые разработчики с открытым исходным кодом так усердно работали над переопределением Ruby, используя разные языки, такие как Java, Python или сам Ruby? Потому что они тоже любят Руби. Они хотят донести это счастье до своей собственной платформы или технологии.