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

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

Мне не нравятся если

Прежде всего, позвольте мне прояснить ситуацию: мне не нравятся операторы if. Я не люблю их вообще, на любом языке. Я мог бы изложить здесь свои причины, но, к счастью, Джон Дегус написал об этом замечательную статью всего несколько дней назад. Проверьте это! Его метод решения проблемы может быть спорным (и он может хорошо применяться только к Haskell), но я согласен с причинами, по которым он уничтожает все if.

Итак, одна из величайших особенностей Erlang (просто шучу :P) заключается в том, что мне никогда не нужно объяснять другим мою концептуальную неприязнь к ifs. Я просто позволяю им проверить синтаксис if, и этого обычно достаточно, чтобы убедить их не использовать его. Позвольте мне показать вам пример…

Положительная печать

Конечно, пример может быть не самым лучшим, но давайте рассмотрим его, так как он показывает почти все проблемы с ifs. Идея очень проста, вы хотите напечатать числа , но вы хотите, чтобы они отображали знак + в качестве префикса, если они положительные. Если у вас нет глубокого понимания того, как ifs работают в Erlang (особенно если вы не имеете опыта функционального программирования), у вас может возникнуть соблазн написать такую ​​функцию:

1> PP = fun(X) ->
1>        if X >= 0 ->
1>          io:format("+")
1>        end,
1>        io:format("~p~n", [X])
1>      end.

Но тогда простой тест покажет вам, что, даже когда он работает для положительных чисел, он терпит неудачу при отрицательном вводе:

2> PP(1).
+1
ok
3> PP(-1).
** exception error: no true branch found when evaluating an if expression

Что тут происходит?

Это одна из тех особенностей функциональных языков: все является выражением, и поэтому оно должно оцениваться как какое-то значение. Таким образом, выражение if X › 0 -› … end должно иметь значение для каждого возможного значения X, если вы хотите, чтобы Erlang вычислил вашу функцию. Функция в том виде, в каком она написана выше, не позволяет вычислить выражение if для отрицательных значений X. Итак, если вы вызываете функцию с отрицательным значением, вы получаете исключение if_clause. Другими словами, если вы действительно не уверены, что никто не вызовет вашу функцию с отрицательными значениями, вам нужно предоставить истинную ветвь для этого if.

истинная ветвь работает как else в других языках, хотя, поскольку if в Erlang — это выражение из нескольких предложений, построенное с охранниками и телами, для этого нет ключевого слова. Вы просто должны обеспечить охрану, которая всегда верна для достижения желаемого эффекта. В документах даже есть такой пример. Давайте улучшим наш собственный пример…

5> PP = fun(X) ->
5>        if X >= 0 ->
5>            io:format("+")
5>         ; true ->
5>            do_nothing
5>        end,
5>        io:format("~p~n", [X])
5>      end.

Давайте протестируем нашу новую функцию…

6> PP(1).
+1
ok
7> PP(-1).
-1
ok

И точно, это сработало!

Но я не хочу настоящих ветвей!

Чаще всего добавление настоящей ветки к каждому if не является чем-то, что разработчик, написавший else-less if в первую очередь хочет сделать. Столкнувшись с этой дилеммой, некоторые разработчики учатся достигать того же результата более функциональным способом, или иначе…

9> PP = fun(X) ->
9>        X >= 0 andalso io:format("+"),
9>        io:format("~p~n", [X])
9>      end.
#Fun<erl_eval.6.52032458>

Да! Используя логический оператор короткого замыкания andalso, они могут добиться эффекта else-less if. Проверьте это!

10> PP(1).
+1
ok
11> PP(-1).
-1
ok

Что, если…?

Удивительно, правда? И вы даже можете обойти ограничения, наложенные операторами Erlang, потому что их предложения являются сторожевыми, а не выражениями. Вы можете использовать любое выражение перед логическим оператором. Вы можете написать такой код внутри своих функций:

…
is_registered(User)
  andalso register_login(User, dates:now()),
…

Но тогда… Что, если вам нужно более одного условия?

is_registered(User)
  andalso password_matches(User, Password)
  andalso
    register_login(User, dates:now()),

Что, если условия немного сложнее?

is_registered(User)
  andalso password_matches(User, Password)
  andalso (has_active_session(User) orelse is_admin(User))
  andalso
    register_login(User, dates:now()),

Что делать, если вам нужно сделать несколько действий, если условия совпадают?

is_registered(User)
  andalso password_matches(User, Password)
  andalso (has_active_session(User) orelse is_admin(User))
  andalso
    begin
      register_login(User, dates:now()),
      open_session(User)
    end

Что делать, если у вас есть вложенные операторы if?

is_registered(User)
  andalso password_matches(User, Password)
  andalso (has_active_session(User) orelse is_admin(User))
  andalso
    begin
      register_login(User, dates:now()),
      is_admin(User) andalso open_admin_session(User)
    end

На самом деле… вы можете использовать трюк, о котором мы узнали неделю назад, верно?

is_registered(User)
  andalso password_matches(User, Password)
  andalso (has_active_session(User) orelse is_admin(User))
  andalso
    begin
      register_login(User, dates:now()),
      [ open_admin_session(User) || is_admin(User) ]
    end

Еслиэтот чрезвычайно уродливый код не убеждает вас не использовать этот метод, я не знаю, что еще я могу сделать…