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

Я решил, что было бы куда интереснее провести более честное сравнение, с точки зрения разработчика, которому нравятся оба языка и который имеет приличный опыт работы с ними. Например, с PHP и Ruby. И задача здесь не в том, чтобы выяснить, какой из них «лучше». Я лишь хочу подчеркнуть те свойства, которые мне нравятся в Ruby и его экосистеме.

Концептуальные различия


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

Метод, переменная, свойство?


Для доступа к свойствам, методам и переменным в РНР используется разный синтаксис, в отличие от Ruby.

PHP
$this->turtle   # Свойство экземпляра
$this->bike()   # Метод
$apple          # Переменная
Ruby
@turtle         # Свойство экземпляра
turtle          # "Свойство экземпляра" с использованием attr_reader: :turtle
bike            # Метод
apple           # Переменная

Педанты, заметят, что attr_reader :turtle динамически определяет метод, используемый в качестве геттера для @turtle, поэтому turtle и bike суть одно и то же. При этом PHP-разработчик не поймёт откуда что берётся, глядя на использование turtle без явного определения имени метода или переменной.

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

Вы удалили поле, но контракт JSON считает, что оно ещё присутствует, при этом осталось полно кода?

class Trip
  def canceled_at
    nil
  end
end

Это отлично работает: если что-то вызовет trip.canceled_at, то вместо поля получит nil. А позднее это можно корректно удалить.

Указания типов (type hint) против «утиной» типизации


В мире РНР указания типов являются вещью одновременно странной и чудесной. В языках наподобие Go обязательно нужно указывать как типы аргументов, так и типы возвращаемых значений. Опциональное указание типов для аргументов появилось в РНР с пятой версии. Вы также можете запросить массивы, конкретные имена классов, интерфейсов или абстрактных объектов, а также недавно вызванные объекты.

В РНР 7.0 появилось указание типов для возвращаемых значений, а также поддержка хинтинга для int, string, float и т.д. Кроме того, были внедрены скалярные указания типов. Эта функциональность стала причиной многочисленных споров и дебатов, но в результате была реализована. Её использование полностью зависит от предпочтений самого программиста, что является хорошей новостью в свете разнообразия пользователей РНР.

В Ruby ничего этого нет.

«Утиная» типизация — это отличный подход, поддерживаемый частью РНР-сообщества. Вместо того, чтобы заявить: «Аргумент должен быть экземпляром класса, реализующим FooInterface», в результате чего у FooInterface будет метод bar(int $a, array $b), можно сказать иначе: «Аргумент может быть чем угодно, лишь бы реагировал на метод bar. А если не будет реагировать, то придумаем что-нибудь ещё».

Ruby
def read_data(source)
  return source.read if source.respond_to?(:read)
  return File.read(source.to_str) if source.respond_to?(:to_str)
  raise ArgumentError
end

filename = "foo.txt"
read_data(filename) #=> считывает содержимое foo.txt с помощью вызова File.read() 

input = File.open("foo.txt")
read_data(input) #=> считывает содержимое foo.txt с помощью передачи 
		  # в дескриптор файла

Это по-настоящему гибкий подход, хотя некоторым такой код не по душе. Особенно в РНР, в котором int(0) и int(1) в нестрогом режиме (weak mode) считаются корректными булевыми, принимающими любое значение, так что довольно рискованно надеяться, что всё будет работать как надо. В РНР мы могли бы просто определить два разных метода/функции:

function read_from_filename(string $filename)
{
    $file = new SplFileObject($filename, "r");
    return read_from_object($file);
}

function read_from_object(SplFileObject $file)
{
  return $file->fread($file->getSize());
}

$filename = "foo.txt";
read_from_filename($filename);

$file = new SplFileObject($filename, "r");
read_from_object($file);

Иными словами, в РНР можно легко использовать «утиную» типизацию:

function read_data($source)
{
    if (method_exists($source, 'read')) {
        return $source->read();
    } elseif (is_string($source)) {
        $file = new SplFileObject($source, "r"));
        return $file->fread($file->getSize());
    }
    throw new InvalidArgumentException;
}

$filename = "foo.txt";
read_data($filename); #=> считывает содержимое foo.txt с помощью вызова 
                      #   SplFileObject->read();

$input = new SplFileObject("foo.txt", "r");
read_data($input); #=> считывает содержимое foo.txt с помощью передачи 
		    # в дескриптор файла

Очень популярно заблуждение, что Ruby «лучше» РНР благодаря возможности использования «утиной» типизации. РНР позволяет использовать оба подхода, выбирайте любой. И этим РНР выгодно отличается от Ruby, в котором нет возможности применять type hint, даже если вам очень хочется. Многие РНР-программисты очень не любят указания типов и хотели бы, чтобы их вообще не было. К их сожалению, однако, в РНР 7.0 стало ещё больше type hint.

К слову, раньше в Python тоже не было указаний типов, но недавно их всё-таки внедрили.

Забавные возможности


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

Вложенные классы


Для РНР-разработчика это довольно экзотическая вещь. Наши классы обитают в пространстве имён, при этом и класс, и само пространство могут иметь одинаковые наименования. Так что если у нас есть класс, имеющий отношение лишь к какому-то одному классу, то мы просто вносим его в пространство имён. Возьмём класс Box, который может кинуть исключение ExplodingBoxException:

namespace Acme\Foo;

class Box
{
    public function somethingBad()
    {
      throw new Box\ExplodingBoxException;
    }
}

Это исключение должно где-то существовать. Можно положить его поверх класса, но у нас в одном файле два класса… в общем, многих это позабавит. Будет нарушен PSR-1, в котором говорится:

«Это значит, что каждый класс находится в отдельном файле, а в пространстве имён — как минимум занимает один уровень: имя поставщика верхнего уровня»

Вот его отдельный файл:
namespace Acme\Foo\Box;

class ExplodingBoxException {}

Для загрузки этого исключения придётся использовать автозагрузчик и снова обращаться к файловой системе. Но на это тратятся ресурсы! В РНР 5.6 избыточность повторных запросов снижается при включении кэша кода операции (opcode), но всё равно получается лишняя работа.

В Ruby можно вкладывать один класс в другой:
module Acme
  module Foo
    class Box
      class ExplodingBoxError < StandardError; end

      def something_bad!
        raise ExplodingBoxError
      end
    end
  end
end

Это можно делать как при определении класса, так и вне его:

begin
  box = Acme::Foo::Box.new
  box.something_bad!
rescue Acme::Foo::Box::ExplodingBoxError
  # ...
end

Наверное, выглядит странно, но зато очень удобно. Класс имеет отношение только к одному классу? Сгруппируем их!

Другой пример связан с миграциями баз данных. Они используются во многих популярных РНР-фреймворках, от CodeIgniter до Laravel. Если в миграции вы ссылаетесь на модель или другой класс, и потом меняете его, то старые миграции очень причудливо ломаются.

В Ruby эта проблема красиво решается с помощью вложенных классов:

class PopulateEmployerWithUserAccountName < ActiveRecord::Migration
  class User < ActiveRecord::Base
    belongs_to :account
  end

  class Account < ActiveRecord::Base
    has_many :users
  end

  def up
    Account.find_each do |account|
      account.users.update_all(employer: account.name)
    end
  end

  def down
    # Все пользователи, имеющие ID аккаунта, обновляются до предыдущего состояния
    # «работодатель отсутствует»
    User.where.not(account_id: nil).update_all(employer: nil)
  end
end

Вместо глобальных объявленных классов будет использоваться вложенная версия ORM-моделей User и Account. То есть при необходимости они могут выполнять для нас роль снэпшотов. Это куда полезнее, чем вызывать код в условиях, когда правила игры могут измениться в любой момент. Для кого-то всё это прозвучит дико, но только до тех пор, пока не столкнётесь с проблемами миграции.

Отладчик


XDebug — вещь замечательная. Использование контрольных точек (breakpoints) совершило маленькую революцию в отладке PHP-приложений, позволив реализовать более продвинутую по сравнению с широко распространённой среди начинающих разработчиков схемой «var_dump() + обновить».

Иными словами, вам может стоить немалых усилий заставить XDebug работать с вашим IDE, найти правильный аддон (при необходимости), настроить php.ini, чтобы можно было использовать zend_extension=xdebug.so с вашим CLI и веб-версией приложения, получить отправленные контрольные точки, даже если вы используете Vagrant, и т.д.

В Ruby немного другой подход. Как и при отладке JavaScript в браузере, вы можете просто вбить в код слово debugger, тем самым получив контрольную точку. Когда эта строка будет выполняться, не важно, что это — $ rails server, модульный тест, интеграционный тест и т.д. — вам будет доступен экземпляр REPL, способный работать с вашим кодом.

Существует ещё несколько отладчиков, самые популярные из которых pry
и byebug. Оба они являются gem'ами, и для их установки нужно через Bundler добавить в Gemfile код:

group :development, :test do
  gem "byebug"
end

Это аналог зависимости dev Composer. Если вы используете Rails, то после установки достаточно вызвать debugger. В противном случае сначала надо выполнить require "byebug".

В руководстве по Rails рассказывается, как всё работает после внедрения в ваше приложение соответствующего ключевого слова:

[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
 
(byebug)

Стрелка указывает на строку, запускающую экземпляр REPL. Вы можете прямо с неё начинать исполнять свой код. В том месте @articles ещё не определён, но зато можно вызвать Article.find_recent и посмотреть, что будет. Если вылетит ошибка, то можно набрать next и перейти на следующую строку в том же контексте. Либо набрать step и выполнить следующую инструкцию.

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

Unless


Многим не нравится unless. Им часто злоупотребляют, как и многими другими вещами во многих других языках. Unless не даёт людям покоя уже много лет, как подмечено в одной статье от 2008 года.

Структура unless является «антонимом» if. Код блокируется, если условие принимает значение true, и продолжает выполняться при значении false.

unless foo?
  # bar that thing
end

# Or

def something
  return false unless foo?
  # bar that thing
end

Это несколько облегчает работу, особенно при большом количестве условий. Могут использоваться || и круглые скобки. Вместо такой строки:

if ! (foo || bar) && baz 

можно написать:

unless (foo || bar) && baz

Возможно, это будет уже слишком, и вам никто не позволит использовать unless вместе с else, но сам по себе это удобный инструмент. О его внедрении в РНР просили ещё в 2007 году, но какое-то время просьба игнорировалась. Наконец Расмус Лердорф, создатель РНР, сообщил, что функция unless() нарушит обратную совместимость, и это «не будет очевидно для тех, для кого английский язык не является родным».

«Это странное слово, по сути означающее «нет, если», хотя по логике должно быть аналогом того, как «меньше (less)» является противоположностью «больше», но при этом приставка “un” меняет его значение на противоположное (less — unless).»

Спорное высказывание. Когда люди читают слово unless, то они не воспринимают его значение как «противоположное less» только из-за приставки un. Иначе мы бы читали функцию uniqid() и считали её противоположностью iqid().

Предикатные методы (Predicate Methods)


В Ruby есть ряд интересных соглашений, которые по-другому решены в РНР. Одна из них — предикатные методы, то есть методы с булевым типом отклика (boolean response type). Учитывая, что в Ruby не возвращаются указания типов, подобные методы могут быть хорошим подспорьем.

Многие из предикатных методов уже встроены в Ruby, например, object.nil?. По сути, это аналог $object === nil в РНР. Если нужно что-то запросить, а не выполнить действие, то лучше использовать include? вместо include.

Можно задавать и собственные предикатные методы:

class Rider
  def driver?
    !potential_driver.nil? && vehicle.present?
  end
end

Многие РНР-разработчики при создании предикатного метода добавляют к имени префикс is и/или has, в результате получается, например, isDriver() или hasVehicle(). Но иногда можно встретить и другие префиксы. Допустим, метод can_drive? из Ruby может превратиться в canDrive() в РНР, при этом будет не совсем понятно, что это предикатный метод. Лучше переименовать его, в соответствии с традицией, в нечто вроде isAbleToDrive().

Ещё более лаконичный синтаксис массивов


В РНР можно легко определять литеральные массивы, и начиная с РНР 5.4 появился ещё более лаконичный синтаксис:

// < 5.4
$a = array('one' => 1, 'two' => 2, 'three' => 'three');
// >= 5.4
$a = ['one' => 1, 'two' => 2, 'three' => 'three'];

Кто-то может сказать, что в данном случае с краткостью несколько переборщили. В Ruby 1.9 появилась опция, позволяющая заменять => на точку с запятой. В РНР можно было бы пойти ещё дальше:

$a = ['one': 1, 'two': 2, 'three': 'three'];

В ряде случаев это было бы действительно удобно, например, когда приходится несколько сотен раз за день набивать вложенные массивы. Предложение по упрощению синтаксиса было размещено ещё в 2011 году, но до сих пор не принято. Возможно, что и не примут. В РНР стараются сохранить минимализм синтаксиса и очень редко внедряют новые способы реализации старых вещей, даже если новинка обещает немало преимуществ. Можно сказать, что синтаксические пряники не являются приоритетом для команды РНР, в то время как в Ruby это едва ли не главное направление.

Объектные литералы


Эту функциональность, имеющуюся в Ruby, многие разработчики хотели бы видеть и в РНР. Если здесь вам нужно определить класс StdClass со значениями, то у вас есть два пути:

$esQuery = new stdClass;
$esQuery->query = new stdClass;
$esQuery->query->term = new stdClass;
$esQuery->query->term->name = 'beer';
$esQuery->size = 1;
 
// или

$esQuery = (object) array(
   "query" => (object) array(
       "term" => (object) array(
           "name" => "beer"
       )
   ),
   "size" => 1
);

В РНР так было всегда, но ведь можно делать и гораздо проще. Скажем, почти целиком позаимствовав синтаксис из Ruby:

PHP
$esQuery = {
   "query" : {
       "term" : {
           "name" : "beer"
       }
   },
   "size" : 1
};

Ruby
esQuery = {
   "query" : {
       "term" : {
           "name" : "beer"
       }
   },
   "size" : 1
}

Очень хотелось бы увидеть это нововведение в РНР, но пока что интереса со стороны разработчиков нет.

Метод Rescue


В PHP есть try/catch, а в Ruby ? begin/rescue. Работают они практически одинаково, особенно в свете появления в PHP 5.6 finally, как аналога ensure из Ruby. В обоих языках есть возможность восстановления после исключения из любого места, для чего служат команды try и begin. Но Ruby позволяет делать гораздо больше: вы можете пропустить метод begin и восстановиться напрямую из тела функции/метода:

Ruby
def create(params)
  do_something_complicated(params)
  true
rescue SomeException
  false
end

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

К сожалению, в РНР этот подход не работает, но если бы его реализовали, то код мог бы выглядеть примерно так:

function create($params) {
  do_something_complicated($params);
  return true;
} catch (SomeException $e) {
  return false;
}

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

Повторные попытки после исключений


Очень удобным инструментом является команда retry:

begin
  SomeModel.find_or_create_by(user: user)
rescue ActiveRecord::RecordNotUnique
  retry
end

В этом примере соревнование между языками снова обостряется благодаря неатомарности find_or_create_by (ORM сначала выполняет SELECT, а затем INSERT). И если вам не повезёт, то другой процесс может создать запись после SELECT, но до INSERT.

Раз уже такое может произойти, то лучше с помощью SELECT предусмотреть возможность повторной попытки выполнения begin...rescue. Возможно, в каких-то случаях вы даже захотите поместить сюда некую логику, чтобы прогнать её один-два раза, но мы не будем рассматривать этот вариант. Давайте лучше оценим удобство повторного исполнения какого-то куска кода. В Ruby это можно сделать так:

def upload_image
  begin
    obj = s3.bucket('bucket-name').object('key')
    obj.upload_file('/path/to/source/file')
  rescue AWS::S3::UploadException
    retry
  end
end

А в PHP сначала потребуется создать новую функцию/метод для содержимого начального блока:

function upload_image($path) {
  $attempt = function() use ($path) {
    $obj = $this->s3->bucket('bucket-name')->object('key');
    $obj->upload_file($path);
  };
  
  try {
    $attempt();
  } catch (AWS\S3\UploadException $e)
    $attempt();
  }
}

Возможно, в обоих случаях вы захотите использовать какие-то шаблонные заготовки для остановки цикла. Но с точки зрения повторного исполнения код Ruby получается гораздо более чистым. Ходят слухи, что сейчас ведётся активная работа над этой функциональностью. Возможно, мы увидим её в версии РНР 7.1.

Несколько мыслей напоследок


Не так давно я подметил за собой, что пишу на Ruby так же, как на РНР. Но совместная работа с сильными и опытными Ruby-разработчиками научила меня ряду характерных, несколько отличающихся подходов. Так что в этой статье я отразил то, чего мне не хватало бы в этом языке, если бы я вернулся к РНР, но не на столько, чтобы удержать меня от возвращения. Многие РНР-хейтеры игнорируют нововведения, внедряемые в РНР. И пусть здесь нет каких-то удобных и полезных вещей, свойственных Ruby, но зато в РНР 7 присутствует немало других очень интересных возможностей.

В последние годы РНР заметно улучшился с точки зрения консистентности, в частности за счёт единообразного синтаксиса переменных (uniform variable syntax), контекстно-зависимого лексического анализатора (context-sensitive lexer) и дерева абстрактного синтаксиса (abstract syntax tree). Всё это позволяет РНР быть гораздо более консистентным вне зависимости от степени консистентности стандартной библиотеки.

Поскольку стандартная библиотека в обозримом будущем вряд ли подвергнется улучшениям, то стоит только приветствовать появление новых удобных возможностей и синтаксических приёмов. Другие языки могут сколько угодно экспериментировать, пробовать разные теории и новинки, давая пищу для новостных лент программерских ресурсов. А РНР будет заимствовать именно то, что нужно этому языку. Это эффективный подход. Можно считать РНР пиратом-мародёром.

Если у вас есть время и желание, то экспериментируйте с самыми разными языками. Ruby может быть хорошим началом, Go может быть весьма забавным, а Elixir запутан, но увлекателен. Но это не призыв метаться от одного к другому. Можно уделять каждому языку достаточно времени, чтобы войти во вкус, свыкнуться и привести свой разум в тонус.

Комментарии (80)


  1. vaniaPooh
    06.04.2016 11:53
    +1

    Как вы думаете, на картинке PHP — это слон, а почему Ruby — собака? И кого изображают чайки? :)


    1. malinichev
      06.04.2016 12:10
      +4

      Наверное это «Слон и Моська»)



    1. sajtpro
      07.04.2016 00:47

      Чайки — это идеальный код.


  1. NLO
    06.04.2016 12:10

    НЛО прилетело и опубликовало эту надпись здесь


  1. 0xy
    06.04.2016 12:52
    +1

    Лишние буквы можно убрать и получится:

    $esQuery = (object) [
       "query" => (object) [
           "term" => (object) [
               "name" => "beer"
           ]
       ],
       "size" => 1
    ];
    


    1. domix32
      06.04.2016 22:32

      Эх, еще б писать лишние три, чтоб не писать этот знак доллара


  1. JSmitty
    06.04.2016 12:52
    +1

    Вот это:

    function create($params) {
      do_something_complicated($params);
      return true;
    } catch (SomeException $e) {
      return false;
    }
    

    выглядит очень некрасиво и невнятно. Сахар ради сахара. Опять же, PHP здесь ориентируется скорее на «больших» — C++ / Java, там такое не используется.


  1. Blumfontein
    06.04.2016 13:31
    +2

    >> Многим не нравится unless

    Когда начинал только программировать на ruby, при встрече в чужом коде с unless мой «парсер кода» начинал жутко тормозить и уходить в свап.


    1. ShNURoK42
      06.04.2016 18:06

      Всего лишь дело привычки.


  1. jrip
    06.04.2016 14:10
    +4

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


  1. stnw
    06.04.2016 14:46

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


  1. boolive
    06.04.2016 15:04
    +1

    Зачем литеральные объекты в php?


  1. denis-vc
    06.04.2016 15:33

    Не претендуя на полноту и объективность…

    Буквально недавно пересел с Symfony2 на Rails4.
    * unless скорее порадовал
    * byebug и REHL очень порадовал (потому что app-сервер в дебаг-режиме еле ползает, впрочем как и php5.5-fpm + Symfony2).
    * жизнь без интерфейсов и type hinting — боль и тонны загадочного кода
    * всякие динамические акссессоры и «вопросительные» методы где-то радуют, а где-то бесят. Вообще не кажется удобным, что каждый объект обладает невообразимой тучей методов и свойств, на все случаи жизни. Причём часть из них IDE не находит, хотя и поскрипывает по паре секунд на каждый запрос автокомплита.
    * Жизнь без DI кажется ненадёжной и иллюзорной.
    * RVM штука классная, но на фоне развития Docker-контейнеров уже не смотрится killer future.
    * bundler по сравнению с Composer слабоват. Может я ещё не всё про него узнал
    * конкретно Rails скорее разочаровал. До этого подходил к нему серьёзно ещё на второй версии. На фоне прогресса Symfony с первой до третьей версии, изменения в рельсах за это время ничтожны (включая и пятую версию тоже).

    Но больше всего угнетает ActiveRecord и миграции. Раньше даже не осознавал, насколько это ловко сделано в Doctrine[Migrations] и сколько боли доставляет отсутствие «полной» конфигурации объектного мэппинга в рельсах.


    1. XimikS
      06.04.2016 17:46

      Посмотрите в сторону Sequel. Замечательная вещь.
      Еще есть интересный развивающийся ROM.
      И можно поподробнее о преимуществах Doctrine[Migrations]? Звучит любопытно.


      1. Fesor
        07.04.2016 02:58

        И можно поподробнее о преимуществах Doctrine[Migrations]? Звучит любопытно.


        Тут скорее преимущество доктрины и data mapper против active record. OO-first, генерация схемы их мэппингов сущностей и соответственно вместо ручного написания миграций частенько достаточно сделать migrations:diff.

        Но вроде как для RoR тоже есть реализация data mapper вместо active record.


      1. denis-vc
        07.04.2016 09:48

        1) DB-first. Унаследованное приложение, нужно сгенерить сущности и связи из существующей схемы.

        рельсы могут сделать файл schema.rb, которые по сути тот же sql дамп схемы, но на DSL. Делать модели — это уже дополнительные гемы неизвестной кривизны. Ввиду отсутствия явных атрибутов моделей и их типов, сама задача не так актуальна. Нафигачить руками has_one, belongs_to… — это не суперподвиг.

        доктрина скафолдить модели умеет искаропки (а симфони на их основе скафолдит весь CRUD). Конфигурация мэппинга может храниться в классах аннотациями или конфигах (xml, yaml). Каждый вариант бывает сильно удобнее в каких-то конкретных кейсах. В конфиге указываются как связи, так и типы данных.

        2) Model first — когда модели, их мэппинг и данные описаны… минутку но в рельсах можно описать данные только в миграциях через DSL, который по сути только обёртка над SQL DDL. Негде описать какой тип данных ожидает *модель*. Сгенерить полноценный diff в такой ситуации — просто нереальная задача. Данных недостаточно. Есть средства, которые позволяют как-бы восстановить картину по истории миграций, но они будут бессильными, если какие-то модификации проведены без миграций или описаны через sql. DSL, кстати по умолчанию (по соглашению, ага) не индексирует и не создаёт fk на связочные поля. Ну ладно консистентность (я это правда сказал?!), но это же тормозит!!!

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

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

        schema.rb перегенерится после каждой миграциии и это источник постоянных конфликтов мержей. Поэтому в нашем проекте я его вообще пока сунул в gitignore. Пока не придумал, как его правильно готовить, без головняков на ровном месте. Скорее всего будем после релиза его генерить на проде в какой-нибудь schema.rb.prod, а потом при разворачивании девелоперских и тестовых сборках, переименовывать в schema.rb и стартовать с него.

        В доктрине миграции — это служебный код, который, после срабатывания миграции, можно безболезненно удалить (и написать delete на табличку schema_migrations, чтобы вычищались неактуальные версии миграций). Был бы дамп с достаточно свежей версией. Нет дампа — тоже не беда. Можно сгенерить нулевую миграцию относительно пустой базы. И начать историю с чистого листа.

        Про отличия ActiveRecord от DataMapper я не буду распространяться, кроме того, что для сложных задач, второй безусловно предпочтительнее.

        Это, конечно, не всё, что я имею сказать по этому поводу, но на этом уже можно остановиться…


    1. summerwind
      07.04.2016 01:08

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


      1. sashaaro
        07.04.2016 07:37

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


      1. Fesor
        07.04.2016 10:13

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


        1. summerwind
          07.04.2016 13:45

          Я не буду спорить насчет «persistence ignorance», но комментарий я писал именно по поводу миграций.


          1. Fesor
            07.04.2016 16:15

            А мой комментарий о том что «возможность генерить миграции» это приятный бонус но не киллер фича.


  1. Promix17
    06.04.2016 15:40
    -1

    По поводу отладки. В rails есть такая вещь как WebConsole. Очень удобно. Отлаживать ошибки прямо в браузере. Сомневаюсь, что на PHP когда-нибудь это будет возможно.
    image


    1. denis-vc
      06.04.2016 15:53

      Это удобно только если не рассматривать в качестве альтернативы полноценный debug-режим. Который позволяет не вставлять отладочный код в текст, а пользоваться брек-поинтами в IDE.

      Что же касается веб-панелей, то я просто тащусь от симфонийской: symfony.com/blog/new-in-symfony-2-8-redesigned-web-debug-toolbar
      Потому что в ней не только информации в разы больше, без всяких вмешательств в код, но есть ещё и профилировщик встроенный (которому ещё можно задавать дополнительные точки фиксации). Причём всё это с историей и независимыми репортами на каждый запрос (включая AJAX), а не так, что эксепшены в соседних окнах чистят сессию друг другу.


      1. Alexufo
        06.04.2016 16:12

        yii2 тоже утащил себе подобную панель
        dab1nmslvvntp.cloudfront.net/wp-content/uploads/2014/08/1408964113Yii2_debugger.png


    1. Invision70
      06.04.2016 22:18
      +1

      Использую better_errors, на мой взгляд удобнее
      https://github.com/charliesome/better_errors


    1. craft37
      07.04.2016 00:47
      +1

      Также есть вариант в виде использования гема better_errors github.com/charliesome/better_errors

      camo.githubusercontent.com/3fa6840d5e20236b4f768d6ed4b42421ba7c2f21/68747470733a2f2f692e696d6775722e636f6d2f367a42474141622e706e67


  1. antness
    06.04.2016 15:48
    +1

    а не злиться и не звонить кому-то, чтобы с этим разобрались
    Оригинал:
    instead of bubbling up and forcing the callee to handle it
    Возможно, речь всё-таки о выбрасывании исключений вверх по стеку?


  1. LimeOrange
    06.04.2016 17:40
    +2

    Не холивара ради, просто факт:

    I proudly present: The whole experience of programming in PHP, condensed into just two lines.

    Two function definitions from PHP's standard library:

    array_filter($input, $callback);
    array_map($callback, $input);
    Yep.

    © Richard Wossal, http://r-wos.org/blog/php-explained

    «Никогда мы не будем братьями» (с) ;)


    1. zapimir
      06.04.2016 20:27

      И что? Кто сказал что в этих функциях должны быть одинаковые аргументы? Тем более что в полном виде это выглядит так
      array_filter ( array $array [, callable $callback [, int $flag = 0 ]] )
      array_map ( callable $callback, array $array1 [, array $… ] )

      И в array_map можно передавать несколько массивов, а array_filter можно использовать без $callback чтобы удалить все элементы массива у которых false значения.

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


      1. LimeOrange
        06.04.2016 22:00
        +2

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

        И да, лучше бы сделали наконец версию без обратной совместимости, но по-человечески, чем тащить надцатилетнее говно сквозь века.


        1. zapimir
          07.04.2016 01:23

          Ну так он вообще не как язык начинался, а как набор скриптов, чего тут удивительного?
          Тем не менее, где например CMS с популярностью уровня Wordpress на правильных языках? Если в TOP-10 CMS хоть одна не на PHP? Так что PHP при всём своём несовершенстве отлично подходит для своей ниши. При том, что Ruby появился в том же 95 году, что и PHP, а Python вообще в 91-м. Может не в правильности и феншуйности языка дело?


    1. Fesor
      07.04.2016 03:06

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

      use function \myLittlePhp\array\{map, reduce, filter};
      
      $sum = reduce(
          map(range(1, 42), function ($i) {
             return $i * 2;
          }), function ($sum, $i) {
              return $sum + $i;
          }, 0);
      


      но прикольнее было бы добавить pipe-оператор или что-то типа bind а так же короткую запись для лямбд:

      use function \myLittlePhp\array\{map, reduce, filter};
      
      $sum = map(range(1, 42), $i ==> $i * 2)
          |> filter($i ==> $i % 4 === 0)
          |> reduce(($sum, $i) ==> { $sum + $i }, 0);
      


      1. amakhrov
        07.04.2016 11:49

        Короткие лямбды (arrow function) рассматриваются:
        wiki.php.net/rfc/arrow_functions

        К сожалению, синтаксис все еще не такой уж короткий:
        $result = array_map(function($x) => $x * 2, $elements);

        Для сравнения, в Hack (статически-типизированный диалект php от Facebook):
        docs.hhvm.com/hack/lambdas/introduction
        return $people->map($name ==> $name. " Banks");


        1. Fesor
          07.04.2016 12:00

          Если вы не заметили — я как раз таки привел в пример синтаксис Hack.

          Мне текущая RFC по лямбдам категорически не нравится ибо оно бесполезно чуть более чем полностью. Я до сих пор не понимаю почему просто не перетащить эту фичу напрямую из Hack. Технических ограничений (если не говорить про автоматический импорт переменных в скоуп функции) как бы нет.

          Да даже ситуация с "=>" странная. По сути кейс с двусмысленностью легко обходится скобками:

          $x = 2;
          [
              $x => $x ** 2, // просто выражение 2 => 4
              ($x => $x ** 2) // лямбда в качестве элемента массива
          ]
          


          хотя может я не не учитываю другие сценарии. В любом случае можно попробовать лямбды запилить на yay.


          1. amakhrov
            07.04.2016 18:10

            Действительно же hack. Меня смутил приведенный вами пайп-оператор — и я решил, что весь пример чисто синтетический.

            Я так понял, что в php проблемы с запиливанием короткого синтаксиса именно с имеющимися парсером, который иначе пришлось бы сильно менять.
            В Hack парсер писали с нуля, и у них такой проблемы не возникло.

            Согласен, что предложенный вариант лямбд не идеален.


            1. Fesor
              07.04.2016 20:15

              Я так понял, что в php проблемы с запиливанием короткого синтаксиса именно с имеющимися парсером


              Нет, в php7 контекстно-зависимый лексер + парсер с использованием AST так что разобрать можно любую синтаксическую конструкцию. Споры разгорелись из-за тайп хинтинга и семантики в случае массивов. Ну и есть определенные нюансы в плане семантики работы автоимпорта переменных. Как такового механизма скоупов в php как бы нет, но это обходится.

              Нашлись люди которые начали кричать мол «не красиво не понятно будет смущать умы людей». Потому я все надеюсь на проект yay, препроцессор для php, который позволяет реализовывать такие фичи на уровне препроцессора и давать намного большим массам людей «обкатать» фичу.


    1. jrip
      07.04.2016 16:10

      Лично я когда вижу — слышу про подобные «ужасные изъяны PHP», автоматически делаю для себя отметку — человек PHP явно не знает, а скорее всего вообще не программист, и мило улыбаюсь :)
      Не холивара ради, косяки да есть, но вот именно это этот момент — полная фигня, к которой мигом привыкаешь / не замечаешь т.к. юзаешь IDE.


      1. LimeOrange
        07.04.2016 16:17

        «а скорее всего вообще не программист», какой милый вывод. Просто умиляюсь. Куда там Шерлоку ;)
        На всякий случай — программист с 13-летним стажем, начинал с Perl и PHP, уже лет 8 пишу в основном на ruby и scala и вспоминаю PHP как страшный сон.


        1. NLO
          07.04.2016 16:25

          НЛО прилетело и опубликовало эту надпись здесь


          1. LimeOrange
            07.04.2016 17:53

            свистюлек накрутили, а разное говно как было, так и осталось, и тянется уже -надцать лет и будет тянуться ещё -надцать. Потому что за 8 лет никто не набрался смелости вычистить эти Авгиевые конюшни и сделать нормальный обратнонесовместимый релиз.


            1. shoomyst
              07.04.2016 18:15
              +3

              И получить python3? :)


            1. Fesor
              07.04.2016 20:18
              +1

              сделать нормальный обратнонесовместимый релиз.


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

              Вместо этого достаточно сделать «обертку» над этими функциями (а почему до сих пор никто не сделал интересно?), а opcache бы со временем невилировал бы оверхэд от этого дела.


        1. Fesor
          07.04.2016 16:29
          +1

          вспоминаю PHP как страшный сон.


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


        1. jrip
          07.04.2016 16:49

          Почему все кто переходит на ruby становятся такими снобами? :)
          Однако писать можно и 20 лет, это ни о чем не говорит. А вот стенания о какой-то там мелочи в контексте «ну все язык ужасен», это как раз говорит. Как минимум, при должном опыте становятся известны намного более интересные косяки, а в такой момент про какие-то мелочи вспоминать уже язык не повернется. И опять же у слабо знакомого c PHP, но опытного в чем-то другом желания громогласно обсуждать язык по всяким мелочам не появится.


      1. alxalx
        08.04.2016 17:00

        «Ужасных изъянов» сейчас уже действительно нет, и это очень хорошо, времена PHP 5.2 вспоминаются с ужасом. Но конкретно этот момент с неконсистентностью параметров все-таки раздражает даже несмотря на то, что IDE подсказывает правильный порядок. В Ruby с этим дела обстоят намного лучше. Пишу и на том, и на другом, больше на PHP (вынужденно), но Ruby доставляет несравнимо больше удовольствия.


        1. jrip
          08.04.2016 18:02

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


          1. Fesor
            08.04.2016 18:26

            Логичнее начать хотябы с раздражения символом $


            Нынешний лексер между тем позволяет избавиться от этого символа. Но на это не пойдут потому как слишком большой ментальный шифт в языке. Похапэ без доллара — это как бы намекает на возможные негативные эффекты для бизнеса.


            1. jrip
              08.04.2016 18:35

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


  1. kvasvik
    06.04.2016 18:06
    +2

    Пожалуй единственное чего мне не хватает в PHP это синтаксиса анонимных объектов из JavaScript. Ну и чтобы наконец выпилили доллары, а то уже поднадоело их писать) В остальном не вижу никаких особых фич в приведённом списке. «Повторные попытки после исключений» так и вовсе весьма спорное преимущество. Операция не удалась — попробуем ещё раз. А если она опять не удалась? Проще и правильней, сразу написать обработчик, который будет или ожидать успешного завершения в цикле или выпадет с ошибкой. Главными преимуществами PHP на мой взгляд являются его Си подобный синтаксис и широкий выбор CMS/фреймворков, что и определяет его популярность.


  1. JPEG
    06.04.2016 21:40

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


  1. shoomyst
    06.04.2016 23:53

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


  1. Tab10id
    07.04.2016 01:21
    +2

    А меня вот давно мучает всего лишь один вопрос… А есть существуют ли вообще люди добровольно переехавшие с ruby на php??


    1. random1st
      07.04.2016 09:19

      Не знаю, как насчет Ruby, но вот среди питонщиков таких не встречал точно.


      1. LimeOrange
        08.04.2016 15:24
        -1

        да и среди рубистов никого такого не знаю. С PHP на Ruby — пожалуйста. Обратно — ни одного.
        Пожалуй, это кое о чем да говорит. :)


        1. Fesor
          08.04.2016 15:47
          +1

          С PHP на Ruby — пожалуйста. Обратно — ни одного.


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


          1. jrip
            08.04.2016 19:46
            -1

            >А на что потом рубисты перебираются интересно?
            И внезапно никуда не переходят, тупиковый тип получается. Может поэтому они такие злые? Бугога.


          1. LimeOrange
            08.04.2016 20:52

            Из тех что я знаю — scala, haskell, clojure, некоторые node.js увлекаются.


          1. stalkerg
            10.04.2016 23:17

            Мне кажется куда душа ляжет. Про Python я то же не очень согласен, так как в отличии от ruby/php это не web-only язык (в сравнении конечно). К слову Go уже становится не модно, а Rust немного вырастает как и Elexir.

            PS ну и всё же async statefull код (ака websockets) удобнее писать на Python3/Elexi/NodeJS, в PHP/Ruby с этим куда хуже.


            1. Fesor
              10.04.2016 23:46

              async statefull код


              Интересно почему в этот список не попал go, у него для этого самая удобная и простая модель выполнения. В целом же я с вами согласен, хотя возможность использовать корутины есть как в ruby так и в php. А стало быть можно достич примерно того же уровня. Проблема у PHP тут скорее в том, что подавляющее большинство существующих решений расчитаны на использование при умирающей модели выполнения. И пройдет еще год-два прежде чем это хоть как-то поменяется. В Ruby с этим намного проще.


              1. stalkerg
                11.04.2016 00:49

                В Go асинхронность неявная (gorutine). Насколько я понимаю, языки с явной асинхронностью это Python, C#, NodeJS (и возможно будет Rust).
                Я не говорю, что это плохо, просто есть разница между написанием по сути multi thread кода (Go) и асинхронного пусть даже с async/yield.


                1. Fesor
                  11.04.2016 01:30

                  Давайте для начала разберемся что есть «асинхронность». По сути в контексте вопроса, это когда у нас поток выполнения идет не в том порядке, в котором у нас все записано в коде. Мол вызвали мы неблокируемую функцию с await, по коду у нас дальше код наш должен идти, а оно что-то другое запустило, и уже потом наш, когда вызов завершится на самом деле. В этом случае «явная» асинхронность достигается только за счет колбэков/сигналов.

                  Что нам дают корутины (await/yield) — возможность писать код в синхронном стиле, перекидывая обязанности по переключению контекста на рантайм языка, однако все ограничено одним ядром процессора (одним потоком исполнения). Потому допустим надо поднимать несколько инстансов ноды на проект что бы хоть как-то оживить наш web-socket чатик.

                  Что дают потоки? Потоки дают такую-же изоляцию, возможность выполнять код паралельно и использовать весь CPU, но перелючение контекста уже идет на уровне операционной системы и это сильно дорого в плане потребления ресурсов. Так что 10000 отдельных потоков могут даже медленее одного потока.

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

                  Вывод — горутины это так же неявно как и await/yield но намного удобнее и производительнее.


                  1. stalkerg
                    11.04.2016 01:42

                    Вывод — горутины это так же неявно как и await/yield но намного удобнее и производительнее.

                    Вот тут я несогласен. async/yield это кок раз явно т.е. вы явно задаёте когда уходите в event loop. Горутины же больше похоже на микро планировщик из ОС. В Go нельзя явно не уходить в «event loop».

                    Явная асинхронность это когда вы явно в коде задаёте когда уходим в event loop. Это может быть callback или более удобный async/yield.
                    Неявная это когда вы не имеете полный контроль над этим. Мне ближе явный подход.

                    А что если объеденить идею потоков и корутин? мы получим горутины. То есть в go это все те же (ну не совсем, я все же утрирую) корутины. которые собраны в пулы и раскиданы между потоками.

                    Это всё tradeof между удобством и потреблением оперативки. По сути если вы запускаете в несколько потоков Python то получаете аналогичную ситуацию с Go.


                    1. Fesor
                      11.04.2016 11:16

                      В Go нельзя явно не уходить в «event loop».


                      в Go это просто не нужно, а стало быть мы достигаем такого же поведения и нам не нужно учитывать все эти мелочи. По поводу микропланировщика — все именно так. Просто опускаем слово await и все.

                      По сути если вы запускаете в несколько потоков Python то получаете аналогичную ситуацию с Go.

                      Мы сейчас про какие потоки? В Python насколько я помню из коробки есть только псевдопотоки, которые являются просто корутинами. А вот если мы раскидаем эти псевдопотоки по настоящим потокам — то тогда да, примерно так же.

                      И я все еще не понимаю чем вариант go, который самый простой и эффективный в плане работы с I/O, неудобен. Там есть определенные недостатки в экосистеме языка, но это же дело наживное.


                      1. stalkerg
                        11.04.2016 21:27

                        И я все еще не понимаю чем вариант go, который самый простой и эффективный в плане работы с I/O, неудобен.

                        Ну разница как между ASM и C++ т.е. в некоторых ситуациях я сам лучше знаю когда и что нужно прервать.
                        Но тут языки сравнивать мне кажется глупо т.к. Go компилируется хоть и с GC, а Python интерпретируется хоть и байткод.
                        Кроме того, вы точно знаете где можете наткнутся на проблему гонок т.к. переключение явное. Т.е. это бывает полезно + это ещё один интересный подход которого нету в Go.
                        Но это не значит, что подход Go плох, но всё же не стоит делать из него silver bullet.


                        1. Fesor
                          11.04.2016 21:51

                          на проблему гонок т.к. переключение явное.


                          Чушь, вы же не контролируете переключение. Вы можете просто дожидаться завершения неблокируемого вызова или не дожидаться. В go точно так же. Можно ждать а можно не ждать.

                          но всё же не стоит делать из него silver bullet.


                          А никто и не делает, но я не вижу ровным счетом никаких недостатков в модели выполнения go. Круче только erlang.


                          1. stalkerg
                            11.04.2016 22:52

                            Чушь, вы же не контролируете переключение.

                            Ещё как контролирую. Я могу сделать запрос к memcache в блокирующем режиме (в целом как и советую если он по unix-sockets), а уже для тяжёлого SQL запроса сделать yield (и только тогда я верну управление event loop'у).

                            А никто и не делает,

                            Противоречит:
                            я не вижу ровным счетом никаких недостатков в модели выполнения go


                            И вообще не уверен, что всё это сравнивать корректно в Go это просто легковесные потоки (треды), а в Python это синтаксический сахар к event loop машине. Притом, что Threades в Python это скорее аналог горутин (за счёт GIL), хотя и гораздо более примитивный. К слову появились и пулы процессов/тредов для вот такого:
                            https://docs.python.org/3.5/library/concurrent.futures.html#concurrent.futures.Executor

                            ЗЫ полезность event loop машины можно только показать тем, что nginx по сути использует именно её. Т.е. это вполне рабочее решение со своими плюсами.


                            1. Fesor
                              11.04.2016 23:36

                              Я могу сделать запрос к memcache в блокирующем режиме


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

                              Все вызовы в кнешним ресурсам должны быть неблокируемыми. Даже по unix-сокету (это не сильно то и быстрее tcp через лупбэк интерфейс).

                              Притом, что Threades в Python это скорее аналог горутин (за счёт GIL),


                              Threads в пайтон это неявное использование корутин. А генераторы как раз таки и позволяют организовать корутины на event loop.

                              ЗЫ полезность event loop машины можно только показать тем, что nginx по сути использует именно её.


                              go на уровне рантайма так же использует event loop. И так же как и в nginx, где у каждого воркера свой event loop, в go просто создается парочка потоков со своими event loop, а внутренний рантайм уже раскидывает задачи по ним.


                              1. stalkerg
                                11.04.2016 23:58

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

                                Все вызовы в кнешним ресурсам должны быть неблокируемыми. Даже по unix-сокету (это не сильно то и быстрее tcp через лупбэк интерфейс).


                                Тут всё просто — сходить в memcached за данными быстрее чем отдать это дело в event loop. Питон существо не самое быстрое.

                                go на уровне рантайма так же использует event loop.

                                Ну так весь вопрос в том, работаете ли вы с event loop напрямую или же опосредованно в рамках много поточного программирования. Когда вы пишите код в nginx или поверх libevent вы работает с event loop явно из Си.


        1. shoomyst
          08.04.2016 19:32

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


    1. jrip
      07.04.2016 17:27

      Ну вот, есть такие, кто сделал несколько проектов на ruby, однако большую часть все равно пишет на php.
      На php однако работу найти намного проще, а также и работников.
      В целом то это лишь инструменты, причем оба не идеальные.


  1. Rathil
    07.04.2016 01:35

    Я с Руби не знаком, мне просто интересно, в нем есть удаленная отладка, как это позволяет делать xDebuger? Это реально круто! Не меняя ни строчки кода быстро что-то продебажить даже на удаленном тазике!


    1. Rathil
      07.04.2016 01:37

      Что не мало важно — на окружении той машины!


      1. denis-vc
        07.04.2016 09:58

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

        Я этим не занимался на Symfony2 + PHP5.5, потому что адово тормозило — как я понял, из-за большого количества файлов. Очень хочу как-нибудь попробовать на Symfony3+PHP7.

        На текущем Rails-проекте я это тоже пробовал — тоже сильно тормозит. Время от времени можно, но лучше уж <% debugger %> вставлять. Имхо.


  1. anonymous
    00.00.0000 00:00


    1. anonymous
      00.00.0000 00:00


      1. anonymous
        00.00.0000 00:00


        1. anonymous
          00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00


  1. anonymous
    00.00.0000 00:00