Стендап Сьогодні
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

27.05.2025

Функціональний стиль чи декларативний?

Зробив цікаву помилку. Це код власної перевірки для бібліотеки для тестування RSpec:

# expect(response).to be_foobar
RSpec::Matchers.define :be_foobar do |bar|
  match do |actual|
    check_foo(actual)
    if bar
      check_bar(actual, bar)
    else
      true
    end
  end
end

Помилка тут в тому, що якщо bar немає, то перевірка завжди повертає успіх. Причина наче очевидна, якщо вже про неї знати: блок match повинен повертати true, якщо перевірка пройшла. Це звичайнісінька функція. Тому в мене перша перевірка фактично нічого не робить, бо її результат ніяк не враховується.

Мабуть, я це писав під впливом SwiftUI, де все не так:

VStack {
  Text("Foo")
  if bar != "" {
    Text("Bar \(bar)")
  }
}

Це вірний код: перший рядок буде видимим завжди, а другий — тільки якщо змінна не порожня. Власне, так саме працює й JSX та інші мови розмітки, але SwiftUI відрізняється тим, що це все нормальний код Swift.

Якби це був лісп — наприклад, Clojure - то там не тільки легко зробити макрос, який би виконував такий синтаксис та збирав результати, а й такого “дивного” більше, ніж традиційних функцій. Для простого прикладу наведу протяжні макроси (кложуристи, допоможіть, бо я трохи призабув):

(as-> result _
  (check-foo _)
  (and bar (check-bar _))
)

Але у Swift таких потужних макросів немає. Виявляється, заради SwiftUI придумали більш спеціалізований механізм - Result Builder. За наявністю анотації @resultBuilder він збирає послідовність викликів у масив, а потім надсилає у відповідний інтерфейс. Ось так, не побоялися повністю перевизначати поведінку блоку коду без видимих на те ознак.