Статьи

Code Safari: почти синатра, почти читабельный

Sinatra — это минимальный веб-фреймворк для создания веб-приложений на Ruby с минимальными усилиями. Почти Синатра это то же самое, но только в восьми строках кода . Наверняка какая-то хитрость в движении? Давайте посмотрим на эти строки.

# almost-sinatra/almost_sinatra.rb %w.rack tilt backports INT TERM..map{|l|trap(l){$r.stop}rescue require l} $n=Sinatra=Module.new{extend Rack;a,D,S,$p,q,Application=Builder.new,Object.method(:define_method),/@@ *([^n]+)n(((?!@@)[^n]*n)*)/m,4567,a %w[get post put delete].map{|m|D.(m){|u,&b|a.map(u){run->(e){[200,{"Content-Type"=>"text/html"},[a.instance_eval(&b)]]}}}} Tilt.mappings.map{|k,v|D.(k){|n,*o|$t||=(h={};File.read(caller[0][/^[^:]+/]).scan(S){|a,b|h[a]=b};h);v.new(*o){n.to_s==n ?n:$t[n.to_s]}.render(a,o[0].try(:[],:locals)||{})}} %w[set enable disable configure helpers use register].map{|m|D.(m){|*_,&b|b.try :[]}};END{Handler.get("webrick").run(a,Port:$p){|s|$r=s}} %w[params session].map{|m|D.(m){q.send m}};a.use Session::Cookie;a.use Lock D.(:before){|&b|a.use Rack::Config,&b};before{|e|q=Request.new e;q.params.dup.map{|k,v|params[k.to_sym]=v}}} puts "== almost #$n/No Version has taken the stage on #$p for development with backup from Webrick" 

Вот и все. Конечно, это плотно! Здесь есть несколько драгоценных камней, поэтому давайте начнем собирать их по частям. Чтение запутанного кода — отличный способ углубиться в глубокий конец и по-настоящему разобраться с синтаксисом языка, и к концу этой статьи вы будете готовы переехать и оставить грязные тарелки на диване.

Первая линия

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

 %w.rack tilt backports INT TERM..map{|l| trap(l) { $r.stop } rescue require l } 

Что это за первоначальный синтаксис? Это выглядит как список с заданным вызовом #map , но это не похоже на список, который я видел. Этот тип кода трудно найти информацию, поскольку поиск в пунктуации редко полезен. Учитывая, что он выглядит как массив, я взял пунт и искал «синтаксис ruby-массива», который открыл раздел «массивы» в викибуке по программированию ruby :

% w по сути является сокращением для метода String, когда подстроки разделяются только пробелами.

В приведенном примере используется ' в качестве разделителя», и, используя irb мы можем подтвердить, что можем использовать любую пунктуацию после %w . Выбор того, что использовать, является исключительно вопросом стиля.

 %w'rack tilt' # => ["rack", "tilt"] %w.rack tilt. # => ["rack", "tilt"] %w(rack tilt) # => ["rack", "tilt"] %w!rack tilt! # => ["rack", "tilt"] 

Теперь обратим наше внимание на содержимое списка — в нем, похоже, содержатся как нужные файлы, так и сигналы для спасения. Поскольку rack , tilt и #trap являются недопустимыми именами сигналов, вызов #trap вызовет исключение. Обычно в ruby ​​исключения исключаются явно:

 begin trap("bogus signal name") {} rescue ArgumentError => e puts "Invalid signal name" end 

Ruby также допускает конечную форму, точно так же, как вы можете использовать операторы if .

 puts "Small enough" if line_count <= 10 trap("bogus") {} rescue puts ":(" 

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

Вторая линия

С учетом требований и других преамбул, давайте погрузимся в собственно структуру.

 $n = Sinatra = Module.new { extend Rack; a,D,S,$p,q,Application = Builder.new, Object.method(:define_method), /@@ *([^n]+)n(((?!@@)[^n]*n)*)/m, 4567, a 

Здесь есть несколько интересных техник. Первый — это динамическое определение Module . В ruby ​​все является объектом, даже модулями и классами, поэтому, когда вы обычно используете ключевое слово module для определения модуля, вы можете сделать это на лету, вызвав Module.new . Единственное отличие состоит в том, что форма ключевого слова будет иметь явное имя и определенную константу, тогда как последняя будет анонимной.

 module Test end Test.name # => 'Test' Module.new.name # => nil 

Имена переменных с префиксом $ встречаются не часто, поэтому они могут быть вам незнакомы. В результате поиска «типов переменных ruby» была найдена статья с удобной таблицей, в которой обобщены различные префиксы, и в этом случае мы находим, что переменная, начинающаяся с $ является глобальной переменной. Вот почему вы не видите их так часто …

Здесь используются две формы назначения переменных. Тот, который используется в первой строке, основан на том факте, что все в ruby ​​неявно возвращает значение. Вы можете увидеть это в irb — после каждого выполняемого оператора отображается значение, даже если оно часто равно nil.

 # puts returns the value 'nil' irb> puts "hello" hello => nil 

Назначение возвращает назначаемое значение, что означает, что вы можете связать их вместе. В приведенном выше фрагменте это означает, что $n и Sinatra будет присвоен один и тот же Module .

 irb> $n = Sinatara = Module.new {} irb> $n == Sinatra => true 

Другой используемый трюк присваивания называется множественным или параллельным присваиванием, что позволяет назначать разные значения разным переменным в одной строке. Что интересно в этом использовании, так это то, что одна из переменных в левой части a повторно используется в правой части для присвоения константе Application. Я был очень смущен этим некоторое время, но после игры в irb подтвердил, что это на самом деле ошибка в коде!

 a,Application=1,a # => [1, nil] a=1;Application=a 

Линия третья

В этой строке настраиваются методы для различных HTTP-глаголов, и для их понимания нам сначала необходимо ознакомиться с API Rack::Builder , поскольку экземпляр этого класса назначается переменной D Это немного касательно, так что я собираюсь оставить это для будущей статьи, и вместо этого сосредоточусь на двух интересных синтаксических особенностях, которые использует этот фрагмент, оба, относящиеся к лямбдам и представленные в ruby ​​1.9.

 %w[get post put delete].map{|m| D.(m){|u,&amp;b| a.map(u){ run->(e){ [200,{"Content-Type"=>"text/html"},[a.instance_eval(&amp;b)]] } } } } 

Первый — это новый способ вызова lambas и procs .() , #call является псевдонимом #call . Таким образом, общее количество способов вызова лямбды в ruby ​​равно трем.

 add_two = lambda {|x| x + 2 } add_two.call(1) # => 3 add_two.(1) # => 3 add_two[1] # => 3 

Есть тонкие различия:. .() Всегда делегирует #call тогда как [] может быть переопределено отдельно, но по большей части они эквивалентны.

Другой интересный синтаксис: run->(e){} . Это ласково известно как синтаксис «stabby», и это новый способ определения лямбда-выражений, который был введен, чтобы позволить указывать значения аргументов по умолчанию. Для синтаксического анализатора ruby ​​было бы невозможно использовать старый синтаксис run(lambda {|e| }) ( более подробно, если вы заинтересованы ).

 add_two = lambda {|x| x + 2} add_two = ->(x) { x + 2 } # The same! double_or_add = ->(a,b=a|2) { a + b } # Just try that with the old syntax... 

Старый синтаксис будет существовать вечно, но новый способ дает вам менее подробный, более гибкий вариант.

И остальные

Сказал тебе, это было плотно! Целая статья, а у нас всего три строчки. С другой стороны, она на полпути через фреймворк! За короткий промежуток времени мы рассмотрели много интересного синтаксиса:

  • Синтаксис массива %w
  • Конечный синтаксис rescue
  • Анонимные модули, использующие Module.new
  • Цепное присвоение ( a = b = 1 )
  • Многократное назначение ( a,b = 1,2 )
  • .() Синтаксис для вызова блоков кода
  • ->() Стабильный синтаксис для написания лямбд

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

  • Кемпинг , карманный веб-микрофрейм с карманом 4к. Это старая школа, которая существует уже много лет. Он включает в себя неограниченную версию и, как таковой, является относительно мягким введением в неясный код.
  • Он старый, но записи в Международном конкурсе кодов Ruby Code 2005 года, отраженные в этом сообщении в блоге .

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