module_function у Ruby: коли методи модуля доступні як модульні та як функції

Обкладинка допису: module_function у Ruby: коли методи модуля доступні як модульні та як функції
ЗмістНатисність на посилання, щоб перейти до потрібного місця
У Ruby модулі дозволяють організовувати код, але іноді потрібно, щоб метод був доступний як для екземплярів модуля, так і безпосередньо з модуля (як функція). Для цього використовується module_function.

Як працює module_function

  • Робить метод приватним для екземплярів модуля (про private читайте тут).
  • Додає також "модульну копію" методу, яку можна викликати без створення об’єкта.
module MathHelper
  def square(x)
    x * x
  end

  module_function :square
end
Тепер можна викликати метод безпосередньо через модуль:
MathHelper.square(5) # => 25
Або використовувати його всередині інших методів модуля (як приватний):
module MathHelper
  def cube(x)
    square(x) * x
  end

  module_function :cube
end

MathHelper.cube(3) # => 27

Особливості

Приватний для інстансів модуля:
include MathHelper
square(5) # => NoMethodError (бо square став приватним)
Підходить для утиліт
module_function часто використовують у "статичних утилітах", щоб не створювати об’єкт модуля для виклику функцій.
Якщо коротко:
  • module_function робить метод одночасно:
    • Приватним для інстансів (через include)
    • Доступним як функцію через модуль
MathHelper.square(5)  # працює
include MathHelper
square(5)             # помилка
Це зручно для модулів, які мають набір утиліт, як у стандартних бібліотеках Ruby (Math, Enumerable для прикладу).
Але якщо звернете увагу на пункт - приватним для інстансів (через include), то можете спитати, а що з extend та prepend (про ці методи писав тут)? 
  • Метод модуля стає приватним для екземплярів і доступним через сам модуль.
  • Виклик через include не працює напряму (метод приватний).
  • Виклик через extend працює як класовий метод.
  • Виклик через prepend не працює на об’єкті (метод приватний).
Все ще важко зрозуміти? Давайте розглянемо більш детально:
module_function робить дві речі одночасно:
  1. Приватним для екземплярів
    • Якщо ти включаєш модуль (include) або робиш prepend, метод стає приватним для всіх об’єктів, тобто ти не можеш викликати його просто так: obj.method - буде помилка.
    • Ruby так робить, щоб ви не змогли випадково викликати цей метод на екземплярі.
  2. Доступним через сам модуль
    • Ruby створює копію методу на рівні модуля.
    • Тому можна викликати: ModuleName.method - це працює як "статична функція".
Простий приклад:
module Utils
  def greet
    "Hello"
  end
  module_function :greet
end

# Через модуль можна
Utils.greet  # => "Hello"

# Через include — не можна, бо приватний
include Utils
greet        # => помилка NoMethodError
  • Чому extend працює?
    extend додає методи модуля у singleton class класу або об’єкта. Там метод викликається через клас/об’єкт, а не через екземпляр, тобто працює.
  • Чому prepend не працює?
    prepend вставляє метод у ланцюжок предків, але оригінальний метод залишається приватним -> об’єкт не може викликати його напряму.

Цей допис поки що не має жодних доповнень від автора/ки.

28 жовт., 14:42

Як працює &:to_s у Ruby і що таке Symbol#to_proc

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Що таке Proc і Lambda в Ruby?
28 жовт., 15:57

Що таке Proc і Lambda в Ruby?

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Що відбувається, якщо викликати [1, 2, 3].map(&Person)
29 жовт., 17:54

Що відбувається, якщо викликати [1, 2, 3].map(&Person)

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Singleton class (eigenclass) у Ruby: що це і навіщо потрібно
29 жовт., 18:29

Singleton class (eigenclass) у Ruby: що це і навіщо потрібно

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
==, equal?, eql?, === у Ruby: що перевіряють і коли використовувати
29 жовт., 20:47

==, equal?, eql?, === у Ruby: що перевіряють і коли використовувати

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Include, Extend, Prepend у Ruby: як вони працюють і в чому різниця
29 жовт., 21:20

Include, Extend, Prepend у Ruby: як вони працюють і в чому різниця

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Що таке memoization в Ruby?
30 жовт., 10:17

Що таке memoization в Ruby?

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
is_a?, kind_of?, instance_of? — як Ruby перевіряє тип об’єкта?
30 жовт., 19:55

is_a?, kind_of?, instance_of? — як Ruby перевіряє тип об’єкта?

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
&& vs and — різниця в Ruby, яка може зламати ваш код
30 жовт., 20:23

&& vs and — різниця в Ruby, яка може зламати ваш код

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Змінні у Ruby: @, @@ та class instance variable
30 жовт., 20:54

Змінні у Ruby: @, @@ та class instance variable

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska
Різниця між blank?, present?, empty? та nil? у Ruby
30 жовт., 21:06

Різниця між blank?, present?, empty? та nil? у Ruby

Нотатки про Ruby та RoR
Нотатки про Ruby та RoR@kovbaska