Строительные блоки Ruby

http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/
  • Перевод
Хвастаясь классными особенностями Ruby перед непосвященными (или перед партнером по языковому спаррингу), возбужденный рубист часто хватается за «мощный синтаксис блоков» в Ruby. К сожалению, для питониста или джависта остаются непонятными возможности пресловутого «мощного блочного синтаксиса» из-за отсутствия соответствующих механизмов в их языках.

Начнем с того, что мы обычно указываем на Rake, RSpec или Sinatra в качестве примеров удивительного использования блочного синтаксиса:

Copy Source | Copy HTML<br/>get "/hello" do<br/>  "Hello World"<br/>end <br/>
(см. www.sinatrarb.com/intro.htmlприм. перев.)

Питонисты обычно указывают на эквивалентный синтаксис в ответ:
Copy Source | Copy HTML<br/>@get('/hi')<br/>def hello():<br/>  return "Hello World"<br/> <br/>def hello() -> "/hi":<br/>  return "Hello World" <br/>

Хотя версия на Python может и уступает по красоте версии Ruby, но сказать «Ruby имеет больше возможностей» довольно трудно. Рубисты наоборот нивелируют аргумент большой семантической мощи сводя его к внешней красоте, когда используют этот пример из Sinatra.

Рубисты, питонисты и другие разработчики, работающие на ниве веб-разработки, используют общий язык JavaScript. Описывая блоки «внешним» людям, которые владеют JavaScript'ом, мы в качестве примера стремимся привести его функции. К сожалению, это только усиливает непонимание.

Похожая ситуация наблюдается со стороны Ruby, когда PHP или Java объявляет «добавление замыканий», многие из нас не перестают спрашивать «какого типа эти замыкания?»

Перейдем к сути


Давайте перейдем к сути дела и покажем лучший пример полезности блоков Ruby.
Copy Source | Copy HTML<br/>def append(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  File.open(path, "a") do |file|<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return data<br/>end <br/>

Метод File.open принимает блок в качестве параметра. Он открывает новый файл (в режиме «append») и передает открытый файл в блок. Когда тот заканчивает работу, Ruby закрывает файл. Кроме этого, Ruby не просто закрывает файл, он гарантирует, что File будет закрыт, даже если выполнение блока завершается исключением. Давайте посмотрим на реализацию File в Rubinius:
Copy Source | Copy HTML<br/>def self.open(*args)<br/>  io = new *args<br/> <br/>  return io unless block_given?<br/> <br/>  begin<br/>    yield io<br/>  ensure<br/>    begin<br/>      io.close unless io.closed?<br/>    rescue StandardError<br/>      # nothing, just swallow them.<br/>    end<br/>  end<br/>end <br/>

Это означает, что вы можете заворачивать вездесущие идиомы типа try/catch/finally внутрь методов.
Copy Source | Copy HTML<br/># Без блоков<br/>def append(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  begin<br/>    file = File.open(path, "a")<br/>    file.puts YAML.dump(data)<br/>  ensure<br/>    file.close<br/>  end<br/> <br/>  return data<br/>end <br/>

Поскольку Ruby вызывает ensure даже когда исключение случается внутри блока, программист может быть уверен, что Ruby выполнит завершающую логику, спрятанную внутрь метода.
Этот пример демонстрирует хорошее качество реализации lambda-функций. Однако блоки в Ruby превращаются в нечто совершенно иное благодаря одной маленькой дополнительной особенности.
Copy Source | Copy HTML<br/>def write(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  File.open(path, "w") do |file|<br/>    return false if Digest::MD5.hexdigest(file.read) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>end <br/>

Представьте, что запись данных на диск требует довольно много ресурсов, и можно пропустить запись, если MD5 хеш содержания файла соответствует значению функции hash объекта data. Мы вернем false, если метод не произвел запись на диск и true в обратном случае.

Блоки Ruby поддерживают non-local-return (несколько ссылок), что означает, что return изнутри блока ведет себя идентично возврату из оригинального контекста блока. В этом случае возврат изнутри блока возвращает из метода write, но Ruby все равно вызывает ensure для закрытия файла.

Можно представить себе non-local-return как нечто подобное этому:
Copy Source | Copy HTML<br/>def write(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  File.open(path, "w") do |file|<br/>    raise Return.new(false) if Digest::MD5.hexdigest(file.read) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>rescue Return => e<br/>  return e.object<br/>end <br/>
, где Return — это Return = Struct.new(:object).

Конечно, такое должна поддерживать любая разумная реализация lambda-функций, но версия Ruby имеет то преимущество, что возврат изнутри блока выглядит так же, как возврат из обычного метода, и при этом требует гораздо меньше «блеска» для достижения эффекта. Эта особенность также помогает в случаях, когда уже используются rescue или ensure, избегая головоломных комбинаций.

Ruby поддерживает вызов super внутри блока. Представьте, что метод write был переопределен в подклассе, а тот же метод класса-родителя просто берет сырые данные из файла и пишет их в лог.
Copy Source | Copy HTML<br/>def write(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  File.open(path, "w") do |file|<br/>    file_data = file.read<br/>    super(location, file_data)<br/>    return false if Digest::MD5.hexdigest(file_data) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>end <br/>

В чистом сценарии lambda-функции нам потребовалось бы хранить ссылку на self, чтобы потом использовать ее внутри lambda:
Copy Source | Copy HTML<br/>def write(location, data)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  this = self<br/>  File.open(path, "w") do |file|<br/>    file_data = file.read<br/> <br/>    # воображаемая конструкция Ruby нужная без<br/>    # non-local-super<br/>    this.super.write(location, file_data)<br/>    raise Return.new(false) if Digest::MD5.hexdigest(file_data) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>rescue Return => e<br/>  return e.object<br/>end <br/>

В Ruby вы можете также вызвать yield в блок, полученный методом, внутри другого блока. Представьте, что метод write вызывается с блоком, который выбирает какие данные использовать в зависимости от того, является ли файл исполнимым:
Copy Source | Copy HTML<br/>def write(location)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  File.open(path, "w") do |file|<br/>    file_data = file.read<br/>    super(location)<br/>    data = yield file<br/>    return false if Digest::MD5.hexdigest(file_data) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>end <br/>

Это можно вызвать через:
Copy Source | Copy HTML<br/>write("/path/to/file") do |file|<br/>  if file.executable?<br/>    "#!/usr/bin/env ruby\nputs 'Hello World!'"<br/>  else<br/>    "Hello World!"<br/>  end<br/>end <br/>

В чистом lambda-языке, мы бы принимали блок как нормальный аргумент функции и вызывали бы его внутри замыкания:
Copy Source | Copy HTML<br/>def write(location, block)<br/>  path = Pathname.new(location)<br/>  raise "Location does not exist" unless path.exist?<br/> <br/>  this = self<br/>  File.open(path, "w") do |file|<br/>    file_data = file.read<br/> <br/>    # воображаемая конструкция Ruby, нужная без<br/>    # non-local-super<br/>    this.super.write(location, file_data)<br/>    data = block.call(file)<br/>    raise Return.new(false) if Digest::MD5.hexdigest(file_data) == data.hash<br/>    file.puts YAML.dump(data)<br/>  end<br/> <br/>  return true<br/>rescue Return => e<br/>  return e.object<br/>end <br/>

Реальное преимущество подхода Ruby заключается в том, что код внутри блока был бы идентичен в случае, если бы метод не принимал бы блок. Рассмотрим такой же метод, принимающий File вместо location:
Copy Source | Copy HTML<br/>def write(file)<br/>  file_data = file.read<br/>  super(file)<br/>  data = yield file<br/>  return false if Digest::MD5.hexdigest(file_data) == data.hash<br/>  file.puts YAML.dump(data)<br/>  return true<br/>end <br/>

Без блока код Ruby выглядит точно так же. Это означает, что Ruby-программисты могут легче переносить повторяющийся код в методы, принимающие блоки, без переписывания большого количества кода. Это также означает, что использование блока не прерывает нормальную логику и можно создавать новые конструкции «управляющей логики», которые ведут себя почти идентично встроенным логическим конструкциям типа if и while.

Rails хорошо применяет это в respond_to, предлагая удобный синтаксис согласования контента:
Copy Source | Copy HTML<br/>def index<br/>  @people = Person.find(:all)<br/> <br/>  respond_to do |format|<br/>    format.html # default action is render<br/>    format.xml { render :xml => @people.xml }<br/>  end<br/>end <br/>

Благодаря тому, как работают блоки в Ruby, вы можете также вернуться из любого из блоков format:
Copy Source | Copy HTML<br/>def index<br/>  @people = Person.find(:all)<br/> <br/>  respond_to do |format|<br/>    format.html { redirect_to(person_path(@people.first)) and return }<br/>    format.xml { render :xml => @people.xml }<br/>    format.json { render :json => @people.json }<br/>  end<br/> <br/>  session[:web_service] = true<br/>end <br/>

Мы вернулись из HTML format после редиректа, что позволило нам выполнить дополнительное действие (установить :web_service в сессии) для других случаев (XML и JSON MIME-типы).

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

Так почему блоки в Ruby лучше?


Если вы забрались так далеко, давайте рассмотрим еще один вариант использования блоков в Ruby: синхронизацию мютексов.

Java поддерживает синхронизацию через специальное ключевое слово synchronized:
Copy Source | Copy HTML<br/>class Example {<br/>  final Lock lock = new Lock();<br/> <br/>  void example() {<br/>    synchronized(lock) {<br/>      // разные опасные штуки<br/>    }<br/>  }<br/>} <br/>

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

Аналогично, Python требовал использование try/finally до версии Python 2.5, когда была добавлена специальная языковая функция для обработки идиомы try/finally:
Copy Source | Copy HTML<br/>class Example:<br/>  # старый вариант<br/>  def example(self):<br/>    lock.acquire()<br/>    try:<br/>      ... access shared resource<br/>    finally:<br/>      lock.release() # разблокировать, независимо от обстоятельств<br/> <br/>  # новый вариант<br/>  def example(self):<br/>    with lock:<br/>      ... access shared resource <br/>

В случае 2.5, объект, переданный в with, должен реализовать специальный протокол (включая методы __enter__ и __exit__), поэтому выражение with не может быть использовано как общецелевые и легковесные блоки Ruby.
Ruby представляет такую же концепцию использования метода, принимающего блок:
Copy Source | Copy HTML<br/>class Example<br/>  @@lock = Mutex.new<br/> <br/>  def example<br/>    @@lock.synchronize do<br/>      # разные опасные штуки<br/>    end<br/>  end<br/>end <br/>

Важно отметить, что synchronize — это нормальный Ruby-метод. Оригинальная версия, написанная на чистом Ruby, выглядит следующим образом:
Copy Source | Copy HTML<br/>def synchronize<br/>  lock<br/>  begin<br/>    yield<br/>  ensure<br/>    unlock<br/>  end<br/>end <br/>

Тут есть все признаки того, что мы успели обсудить выше. Она блокирует объект, вызывает блок и после удостоверяется, что блокировка снята. Это означает, что если программист Ruby возвращает результат изнутри блока, synchronize сработает правильно.

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

Postscript


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

Некоторые полезные комментарии после статьи



James Edward Gray II:
При использовании Pathname, можно перевести:

Copy Source | Copy HTML<br/>File.open(path, “a”) do |file|<br/># …<br/>end <br/>
в:
Copy Source | Copy HTML<br/>path.open(“a”) do |file|<br/># …<br/>end <br/>


Colin Curtin:
Кое-что, о чем нужно помнить про non-local-return: блок должен иметь доступ к контексту, из которого вы хотите вернуться.

Copy Source | Copy HTML<br/>def a<br/>  yield<br/>end<br/>a{ return  0 } # => LocalJumpError: unexpected return<br/> <br/>def c<br/>  yield<br/>end<br/> <br/>def b<br/>  c { return 1 }<br/>end<br/>b # => 1<br/> <br/>def d<br/>  lambda{return 2}.call<br/>end<br/>d # => 2 <br/>


ecin:
Помните, что разные способы создания замыканий (Proc.new, proc, lambda) не всегда эквивалентны друг другу:

innig.net/software/ruby/closures-in-ruby.rb



Rit Li:
Люблю ruby блоки. Спасибо за статью.

По поводу “Rails vs Django”, есть три вещи, в которых Rails выигрывает:

1) Convention over Configuration.
У Django нет больших файлов конфигурации, только один файл settings.py. Так что Django — это фреймворк с “Easy Configuration,” не “Convention over Configuration.”

2) REST
Rails действительно охватывает REST. Семи-экшеновый контроллер замечателен. Django не имеет встроенного механизма resource/route для REST. Однажды начав REST, вы не пойдете назад.

3) Эко система в Rails
Rails плагины есть для всего. Плюс, есть коммерческая поддержка, книги, блоги, скринкасты и хостинги для Rails. Django действительно этого не хватает.

Постскрипты переводчика

PS могу запостить только в свой блог, потому что не хватает кармы для специализированного, но надеюсь, она будет полезна и я перемещу его, например, в Ruby :)
PPS Если надо — добавлю временные метки для комментариев и комментарии про питон (я его не очень хорошо знаю)
PPPS Пожалуйста, не ругайте меня за содержание статьи — ругайте только за перевод :)
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 46
  • +4
    Тяжело дочитать топик до конца…
    Все равно, спасибо за детальное и качественное сравнение!
    • +1
      Спасибо Yehuda Katz за сравнение ;)
      А мне — в лучшем случае за перевод :)
      Советую дочитать, там просто все. Будешь знать чем руби лучше других ;)
      • 0
        В чем то лучше, а в чем то и хуже…
        Но, так как я использую руби уже более 3-х лет, то для меня плюсов все же больше чем минусов :)
        • +2
          да, в чем руби хуже надо читать в других блогах :)
    • 0
      статье ооооочень нехватает подсветки синтаксиса.

      По переводу:

      Метод File.open принимает блок. Он открывает новый файл (в режиме «append») и выдает открытый файл в блок. Когда блок заканчивает работу, Ruby закрывает файл. Кроме этого, Ruby не просто закрывает файл, когда блок<.strong> завершается, он гарантирует, что File будет закрыт, даже если выполнение блока завершается исключением

      и вот из-за таких мелочей как повторяемые слова, статью сложно дочитать до конца =(
      • 0
        т.е.:

        Метод File.open принимает блок. Он открывает новый файл (в режиме «append») и выдает открытый файл в блок. Когда блок заканчивает работу, Ruby закрывает файл. Кроме этого, Ruby не просто закрывает файл, когда блок завершается, он гарантирует, что File будет закрыт, даже если выполнение блока завершается исключением
        • 0
          Подправил это место, а заодно и несколько других. Надеюсь, теперь читать будет легче, но все равно могли остаться «нерусские» места, пишите еще.
          на счет подсветки — соооогласен :) хорошо хоть <pre></pre> работает. Видимо, не предназначен хабр для таких статей
          • 0
            разобрался зачем ХабраРедактор, следующая будет в нем с подсветкой синтаксиса
          • 0
            еще вариант — вставить картинки с кодом из оригинальной статьи, он там подсвеченный
        • 0
          Блоки/коллбеки/замыкания — очень интересная вещь — но статья абсолбтно нечитаема, и примеры какие-то непонятные :(
          • 0
            что именно непонятно?
            • +1
              > Начнем с того, что мы обычно указываем на Rake, RSpec или Sinatra в качестве примеров удивительного использования блочного синтаксиса:

              Что такое Rake, Rspec и тем более Sinatra (певец вроде такой был)? Непонятно. Что делают идущие далее 3 строчки кода? Тем более непонятно, то есть ясно, что выводят Hello world при каких-то условиях, но не очень ясно, при каких.

              Сама структура предложений выдает перевод, и этот дословный перевод звучит как-то неуклюже в русском варианте.

              Дальше идет штук 10 кусков кода, которые явно что-то делают с файлами, но не очень понятно, что имеенно, и чем они друг от друга отличаются (делать diff в уме не все моугт)

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

              Потому я и сделал вывод, что статья (или ее перевод) плохая и запутанная. С точки зрения читателя.
              • 0
                рейк, рспек и синатра — это инструменты руби. о каждом из них написана книга. непосвященному с первого взгляда действительно непонятно, что это и зачем, но это к автору. могу сказать от себя, что он не ставил перед собой цель дать обзор для непосвященных, а именно для тех, кто понимает язык, но не понимает в чем его основная фишка, преимущество. О том, что блоки в руби — это сила, написано в каждом двухстраничном туториале, но почему это так — никто на самом деле не знает (потому что думает, как правило, что основное преимущество в красоте). Примеры показывают суть возможностей, по два примера на каждую — один на «руби» другой — на «обычном» языке, не имеющем этих возможностей. То есть статья действительно не для новичков. А те, кто хорошо разбирается в руби, пусть за себя скажут сами. Мне было очень полезно прочитать и разобраться в ней, хотя я программирую на руби и рельсах уже три года. Автор статьи является одним из разработчиков ядра рельс 3й версии и (может быть я ошибаюсь) руби 1.9.

                Неуклюжий перевод принимается. Но вопрос остается — какие именно фразы звучат не по-русски? я над этим работаю.
                • 0
                  > рейк, рспек и синатра — это инструменты руби. о каждом из них написана книга

                  Ну так бы и писали, кто не прочел эти 3 книги, к статье может даже не подходить :)

                  > К сожалению, он использует пресловутый «мощный синтаксис блоков», обобщая целый набор возможностей, непонятных для питониста или джависта из-за отсутствия соответствующего механизма в их языках.

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

                  > Описывая блоки «внешним», владеющим Javascript'ом, мы стремимся привести его функции в качестве примера. К сожалению, это только усиливает непонимание.

                  Я неплохо знаю Яваскрипт, но этого предложения не понял абсолютно. Чем «внешним»?

                  > When describing blocks to “outsiders” who share a common knowledge of Javascript, we tend to point at Javascript functions as a close analogue. Unfortunately, this only furthers the confusion.

                  Вот теперь стало понятно. Оказывается, «внешним» — это кому, а не чем.

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

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

                    чаще всего такие польза от таких штук обнаруживается при проектировании фреймворков, когда нужно как можно компактнее и удобнее решать сложные задачи. Разработчики, использующие впоследствии эти фреймворки, чаще всего даже не подозревают о таких концептуальных возможностях языка. То есть оно может даже и попасться в их коде (как
                    format.html { redirect_to(person_path(@people.first)) and return }
                    ), скопированное из какого-то блога, но как оно устроено внутри мало кто сможет объяснить — работает себе, что еще надо?
          • 0
            Спасибо за перевод. На баркемпе 20го будете?
            • 0
              постараюсь прийти :)
            • 0
              Спасибо за перевод!
              • 0
                Я понимаю что Django популярен и по этому с ним сравнивают, но куда было бы интереснее сравнение с Pylons. Мне он намного больше нравится. Касательно Ruby, в нем очень много всякого вкусного, но он так же страдает как и perl. Все эти сокращения конечно красивы, пока не начнешь их комбинировать и тогда понятность кода куда-то уплывает… А «подмешивания» просто убивают, особенно когда проект большой. Это все на любителя. Кстати библиотек там мало по сравнению с питоном, взять туже раскраску синтаксиса или wiki-разметку. Дочитал только до первого примера который ввел меня в ступор. Впрочем как и интерпретаторы. Видимо нужно подключить какие-то либы. Очень надеюсь, что кто-то это прояснит, так как самому интересны эти возможности.
                • 0
                  Про Pylons не слышал. к оригинальной статье есть комментарий, хвалящий Rebol.

                  Самый первый пример — это использование фреймворка Sinatra. Его удобство проявляется тогда, когда нужен сайт из 5-10 страниц. В рельсах это выливается в сложный файл роутинга и один контроллер с несклькими вьюшками, или в несколько контроллеров и соответственно несколько папок вьюшек для них, в то время как они выполняют простейшие действия. Неудобно. Синатра позволяет сделать один файл, который сразу же и роутит, и контроллит, например:

                  # myapp.rb
                    require 'rubygems'
                    require 'sinatra'
                    get '/' do
                      'Hello world!'
                    end


                  это уже заменяет все контроллеры и вьюшки рельс, рисуя хелло ворлд на стартовой странице сайта (она же единственная).

                  пример посложнее:
                  get '/hello/:name' do
                      # matches "GET /hello/foo" and "GET /hello/bar"
                      # params[:name] is 'foo' or 'bar'
                      "Hello #{params[:name]}!"
                    end

                  по адресу localhost:4567/hello/NElias будет надпись «Hello NElias»

                  Дальше можно почитать тут: www.sinatrarb.com/intro.html

                  Автор пишет по мотивам дискуссии на одном из ресурсов (http://news.ycombinator.com/item?id=1097901), поэтому подразумевает, что Синатра известна читателю. Кстати, я сам узнал о ней несколько месяцев назад, к собственному стыду :))) Что ж, предлагаю в первую очередь рубистам (а также синатровцам, рельсовикам и т.д. не отставать от прогрессивного человечества, а там и свой фреймворк забодяжить :)
                  • 0
                    Спасибо, понятно. На целую строчку аж короче… хотя не короче, одинаково :) Вот пример из Werkzeug(аналог Sinatra):
                    @expose('/display/<uid>')
                    def display(request, uid):
                        url = URL.query.get(uid)
                        if not url:
                            raise NotFound()
                        return render_template('display.html', url=url)
                    

                    Зря вы про Pylons не слышали, потому что он аналог Rails. В чем-то лучше, в чем-то хуже. Например ORM в Pylons мощнее. А Rails сравнивать с Django не корректно, у них разная философия.
                    • 0
                      С with дело не имел, но подозреваю, что можно сделать похожий синтаксис. Из примера Ruby:
                      get '/hello/:name' do
                          "Hello #{params[:name]}!"
                      end
                      

                      на Python это будет примерно так:
                      with get('/hello/:name'):
                          "Hello %s!" % params[:name]
                      

                      но едва ли это нужно :)
                      • 0
                        Насамом деле на синатре мног очего интересного можно написать, мне на нем приятнее, что-то делать чем на рельсах.
                      • +2
                        По поводу понятности могу разуверить. Руби очень понятный. И миксины с блоками не так страшны как черт малюет. Сужу по своей конторе где многих кто приходил переучивали на Ruby.
                        • 0
                          Ой, ну тоже можно сказать про то, что генераторы и декораторы портят код. Классовые проперти в питоне работают не как во всех привычных языках (не шарятся между наследниками, то есть не являются для всех классов указателем в одно место; и нет, я считаю, что так и должно быть, но везде же это не так как в питоне).

                          И куда там дели private/public :)) Что за дурацкая конвенция с _meth __meth__ и тд и тп. Кароче религия все это :)

                          Да, и возможность написать:

                          class A(object):
                          pass

                          class B(object):
                          pass

                          a = A()
                          a.__class__ = B

                          Это что вообще такое???

                          Ну, а метакласса это вообще ужас-ужас :))))

                          То есть это религия :))) и ничто больше.
                          • 0
                            > И куда там дели private/public :))

                            В руби private/public тоже ничего не значат. Есть несоклько возможностей вызвать снаружи приватный метод. Более того, это не является хаком, а заложено в язык.
                            • +1
                              > а заложено в язык

                              Точнее — в идеологию. И эта идеология присуща многим языкам. Просто большинство кроме статично-классового ООП и не видали ничего больше. Естественно, легче назвать это «религией» и «откреститься» от этого.
                              • 0
                                даже в плюсах есть возможность вызвать приватный метод (насколько я помню) с помощью friend. Может я ошибаюсь по поводу самой возможности, но то, что это идет в разрез с инкапсуляцией — это точно.
                              • +2
                                Причём здесь Ваше отношение к альтернативной идеологии ООП и «религия»? Python использует делегирующую модель наследования с объектами первого класса. Абсолютно та же картина в Javascript.

                                По поводу «private» и «protected» — у Вас, опять же, неверное восприятие. Инкапсуляция — это усиление абстракции, а не параноидальное сокрытие от «злых хакеров». Использовать сокрытие ради сокрытия — это большая и самая распространённая ошибка, связанная с неверным восприятием инкапсуляции. Тогда как, в первую очередь — это помощь программисту, более абстрактно описывать систему.

                                В Ruby тоже можно свободно получить доступ (если того захочет сам программист) к инкапсулированным данным.

                                Найдите время, прочитайте полностью: dmitrysoshnikov.com/ecmascript/ru-chapter-7-1-oop-general-theory/
                                • 0
                                  Ой, ну говорил же я про другое. Я говорил, что обсуждение подходов к ООП это вещи исключительно религиозные. Потому что на парктике нет возможности убедиться какой из них лучше. Я покрайней мере не знаю, как определить какой подход лучше.

                                  Причем здесь «злые хакеры» я вообще не понял. Покажите мне человека, который считает, что private это «сокрытие от злых хакеров», чтобы мы его смогли нахрен выгнать из профессии.

                                  Я программирую на рельсах, в некоторые периоды я писал на питоне (типа не Django, а просто на питоне). Так что я че-то да понимаю, и даже помню те темные времена, когда ребятя наконец решили использовать C3 для лениризации дерева наследования и вообще родили из себя таки new-style classes.
                                  • 0
                                    Религиозная вера заканчивается, когда появляется понимание (или переходит на другой уровень). Я бы сказал что лучше тот язык, который правильнее реализует изначальные ООП концепции, но они тоже не стоят на месте.
                                    Но из-за таких мелочей (как описанные в посте) язык приобретает возможности реализовывать более специфические тонкости, заметные на более низком уровне, но облегчающие создание фреймворков и работу с ними (хотя в других вещах может быть и усложняющие), как опять-таки тот же респонд_ту.
                                    В питоне будут свои тонкости, способные упрощать работу в чем-то, но для характеристики «лучше-хуже» оснований нет (если удобство кодирования, рефакторинга, подключения расширений, документации, настройки сервера, скорость интерпретатора, масштабируемость не отличается в десятки раз), есть только заложенные в сам язык возможности и обусловленные этим более удобно решаемые задачи. Для одной задачи удобнее этот язык, для другой — этот. Т.е., например, руби благодаря своим возможностям, имеет такие преимущества, а питом — такие. Это объективно. «Лучше-хуже» — субъективизм. Если в дискуссию включить программиста микропроцессоров, он скажет, что хуже руби и питона вообще нет, а для веба — наоборот. Есть набор характеристик языка, который отражает эти возможности. Фреймворки (мне кажется) их часто нивелируют, потому что тоже предназначены для решения специфических задач, и особенности языка чаще видны внутри, чем снаружи, а наружу уже вылазят особенности фреймворков, о которых тоже можно бесконечно спорить «лучше-хуже», но по сути если нет выигрыша по критическим параметрам в разы объективно лучшего найти не получится.
                                    • 0
                                      ). Я бы сказал что лучше тот язык, который правильнее реализует изначальные ООП концепции, но они тоже не стоят на месте.

                                      Вот Erlang очень хороший язык массы задач определенного сорта и он совсем не реализует изначальные концепции ООП :)

                                      Если язык реализует примерно одинаковые концепции ООП, а так же не сильно отличается в скорости и маштабируемости, тот тут уже принимается решение исходя из того, а какого качества и сколько сторонних библиотек есть для языка, а также лично впечатление от его «фишек» и красоты синтаксиса. Оба этих параметра субъективны. Любые субъективные параметры можно тут же записывать в потенциальные причины для религиозных споров.
                                      • 0
                                        вот и я о том же :)
                                        но есть объективные особенности — например ретурн изнутри лямбда-функции. Это дает конкретные возможности программисту. Если они нужны — пусть берет руби, если нужны другие — пусть берет другой язык.
                                        • 0
                                          Ну, значит мы с самого начала говорим об одном и том же. Я лишь сторонник того, чтобы добавить в питоне блоки и многострочные лямбы, ибо я считаю, что это удобно. И многострочных лямбд мне в питоне в задачах, которые я решал, нехаватало.

                                          Но это гнилася тема для обсуждения, ибо обсуждалась она много раз. И совсем недавно в очередной раз поднималась в comp.lang.python
                            • 0
                              Я эту статью не осили дважды. Сначала в блоге Каца, а теперь здесь. Тогда я подумал, что может это из-за того, что это английский, вот Улисс Джойса я же тоже по-английски не осилил. Однако тут получилось как с Улисс. По-русски тоже не осилил.

                              Просто мне кажется, что написана скучно, да и примеры… ну кому из нас в руби нужны мютексы? Кому вообще в руби нужны мютексы, если 90% его использования это Rails (тут Matz может меня минусовать, то есть мог бы, если бы у него был аккаунт на хабре и он научился бы читать по-русски).

                              Дело просто в том, что статья написана для тех, кто на руби не пишет, вот по этому осилить рубисту ее и сложно.

                              Вдобавок, стоит отметить, что ублоков есть самое клевое применение: написание DSL-ей. Да, это все сахар и рюшечки, но красиво и удобно. И второе (это уже камень в огород питона): попробуйте в питоне написать лямбду в несколько строк. А это многострочная лямбда была бы крайне полезна во всяких методах над итераторами, типа map, find и т.п.
                              • 0
                                по сути статья сводится к следующему: блоки в руби умеют
                                • возвращать значение вызвавшей их функции;
                                • вызывать super вызвавшей их функции;
                                • передать управление в другой блок.

                                Если еще короче —
                                • делать return
                                • делать super
                                • делать yield.

                                Но только внутри {} или do...end. Proc.new и lambda такое не позволят.
                                • 0
                                  А еще они сохраняют контекст в котором были созданы. Это _очень_ важно.
                                  Чем отличаются {} или do...end от Proc.new и lambda в вашем понимании? Мне кажется вы не поняли сути.
                                  • 0
                                    Поэтому они и умеют все это делать, поскольку сохраняют контекст, это я понимаю. Я писал комментарий «сверху», а не «снизу». Что именно я не понимаю?
                                    • 0
                                      фраза «Но только внутри {} или do...end. Proc.new и lambda такое не позволят» странна.
                                      • +1
                                        скорее всего, «трудности перевода» ))
                                        Я имел ввиду комментарии после статьи:
                                        Colin Curtin:
                                        Кое-что, о чем нужно помнить про non-local-return: блок должен иметь доступ к контексту, из которого вы хотите вернуться.
                                        и дальше. Т.е. в моем понимании нельзя написать оторванный от контекста Proc или lambda с директивами super, return или yield и надеяться, что он будет работать. Нужен контекст для этих super, return или yield.
                                        • 0
                                          с ретурном оказалось еще хитрее (innig.net/software/ruby/closures-in-ruby.rb):

                                          # I'll spare you the rest of the experiments, and give you the behavior of all 7 cases:
                                          #
                                          # «return» returns from caller:
                                          # 1. block (called with yield)
                                          # 2. block (&b => f(&b) => yield)
                                          # 3. block (&b => b.call)
                                          # 4. Proc.new
                                          # 5. proc in 1.9
                                          #
                                          # «return» only returns from closure:
                                          # 5. proc in 1.8
                                          # 6. lambda
                                          # 7. method

                                          т.е. из блока и Proc return вернет из внешней функции, а из lambda — только из нее (как из настоящей лямбда-функции)
                                  • 0
                                    Лямбда в несколько строк уже заслуживает отдельного имени, имхо. Лямбда используется там, где надо быстренько описать несложно правило преобразования аргументов в результат. Приведите пример, где нужна именно многострочная лямбда? (естественно, честная лямбда).
                                    • 0
                                      Вот я считаю, что не заслуживает она отдельного имени. У меня бывали задачи по обработке всяких там наборов данных. Данные приходили, скажем так, в разных форматах. И перед тем, как начать их обрабатывать иногда приходилось сделать некоторые простенькие процедуры привода их к общему виду. Причем иногда к разному. Не суть. На практике это было так: вот тут чуть подколбасить, вот чуть и отправлять считать. Подколбасить это по-сути map или несколько map-ов c простыми преобразованиями. Преобразования столь не сложные, что выносить их в именованную ф-ию нет смысла (более того, они переодически менялись, так что осмысленно набор преобразований выносить в метод), но иногда одной строчки для преобразования не хватало, так что приходилось создавать идиотскую именнованную ф-ию.
                                    • 0
                                      в дополнению к habrahabr.ru/blogs/ruby/86882/#comment_2619504
                                      в частности, это упрощает рефакторинг кода, рендеры и ретурны внутри respond_to, простую реализацию синхронизации мютексов
                                    • +1
                                      def a
                                        yield
                                      end
                                      a{ return  0 } # => LocalJumpError: unexpected return

                                      Это относится к ключевому слову return. Оно, ведь, не просто опционально (как пишут некоторые статьи); в определённых местах эта опциональность имеет смысл:

                                      def a
                                        yield 10
                                      end
                                      a {|n| n + 10 } # 20
                                      • 0
                                        спасибо, тонкая деталь

                                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.