12.01.10

Smalltalk style lazy initialization in ruby (Proof of concept)

В Smalltalk есть очень полезная особенность, возможность ленивой инициализации значений. До тех пор, пока переменной не установят значение, оно равно nil и не занимает памяти, но на программиста возлагается задача не забыть это самое значение присвоить. Lazy initialization позволяет программисту все-таки забыть про это и не огрести люлей от рантайма.
Штука, в принципе, леко реализуемая почти везде, но в ST она очень круто выглядит и легко читается:

SomeClass>>lazyValue
    ^property ifNil: [property := 'Default'].

В руби для этого придумали целый новый метод ||= описание которого я с первого раза вкурить не смог, когда начинал разбираться в языке. Примерно в то же время я написал для себя библиотечку smalltalk_ifmessages.rb, она очень простая:
class Object
  def ifTrue
    if self
      yield
    else
      self
    end
  end

  def ifNil
    if self == nil
      yield
    else
      self
    end
  end

  def ifNonNil
    if self != nil
      yield
    else
      self
    end
  end
end
С её помощью можно нафигачить в руби леко читаемую ленивую инициализацию:
require 'smalltalk_ifmessages.rb'

class LazyClass
  def lazy_prop
    @property.ifNil { @property = "Default" }
  end
  
  def lazy_prop=(value) 
    @property = value
  end
end

8 коммент.:

ptzn комментирует...

А что сложного в ||=?
@property ||= "Default" – если @property nil, то присвоить "Default"

Ну и чтоб 2 раза не вставать, побрюзжу: очень режет глаз смесь camelcase и underscore стилей именования методов.

Сёмка Новиков комментирует...

Читается магически, так-то ничего сложного.
А что именно раздражает? Это как бы Ruby стандарт, Классы кемелкейзом, методы и переменные андерлайном.

Сёмка Новиков комментирует...

А, ты про smalltalk_ifmessages? Я пытался повторить стиль Smalltalk'а

ptzn комментирует...

Я про @property.ifNil

Anonymous комментирует...

Читается магически, потому что местная идиома. И почему целый метод придумали? Просто прилепили в компанию ко всяким += и иже с ними.
Нда, а в СТ-то красивше...

Зверёк комментирует...

Это, конешно, поучительное упражнение. Но один из самых плохих стилей программирования — не разобравшись в идиомах языка, тащить в него совершенно противоестественные идиомы из другого.
А магии там никакой нету:
x ||= "Default
это просто краткая запись
x = x || "Default"
т.е. «присвоить х значение х или, если оно nil или false, значение "Default"». Сокращённые записи существуют для всех операторов.
i += 1 — тоже выглядит магией?

Один из «элементов стиля и хорошего тона» в руби — DRY (dont repeat yourself), даже в мелочах. Имено поэтому пишут x ||= "Default", а не x = x || "Default"
И именно поэтому @property.if_nil{@property = "Default"} смотрится плохо (дважды повторяется @property).

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

Сёмка Новиков комментирует...

Спасибо огромное, про dry я не подумал, а ведь действительно.
Кстати я и не тащил в руби идиомы другого языка, во-первых это была попытка понять, как лучше (выяснилось, что ||= лучше), во-вторых Smalltak и Ruby очень близкородственные языки. 
Сокращенные операторы смотрятся не магически до тех пор, пока они не превышают некоторый критический уровень концентрации в коде. Например за тернарные операторы надо бы давать по щщам, чаще всего. Не согласны?

Зверёк комментирует...

Smalltalk и Ruby родственные, да. Но это не один и тот же язык. И конкретная идиома ifNil чужда для руби.

А насчёт сокращённых операторов — не знаю, не знаю. В середине сложного вычисления тернарный оператор может и путать. Но как однострочник (например, при возврате результата) вполне удобен. Ну и всякое там a > b ? a : b  — тож ничего страшного (хотя конкретно этот пример по-рубёвому я бы записал скорее [a, b].max)

Отправить комментарий