Ранние возвраты могут быть полезны в языках, основанных на операторах, таких как Java или C #, для реструктуризации кода и устранения вложенности.
При первом использовании F # одной из проблем, с которыми я боролся, было отсутствие ранних возвратов. Концепция раннего возврата не имеет смысла в языке, основанном на выражениях, где мы составляем значения вместе для получения новых значений, а не упорядочиваем утверждения. Что же тогда делать с вложенностью в F #?
Классическое решение: привязка и вычислительные выражения
Вот код, который просматривает две вещи, а затем объединяет их, если они оба имеют значение:
Один уровень вложенности - это неплохо, но вы можете видеть, как это может быстро выйти из-под контроля.
Этот шаблон, при котором мы замыкаем короткое замыкание при сбое или продолжаем успешно работать, достаточно распространен, чтобы мы могли использовать Option.bind
для его инкапсуляции:
Возможно, это немного лучше, поскольку было удалено утомительное сопоставление None -> None
, но оно все еще вложено.
Мы можем использовать вычислительные выражения, чтобы исключить вложенность:
Проницательные читатели заметят, что здесь мы также можем использовать Option.map2
для устранения вложенности, не прибегая к выражению вычисления. Дело в том, что существует множество существующей литературы о том, как работать с таким кодом, когда вы замыкаетесь при неудаче и продолжаете добиваться успеха.
В глуши
Однако иногда вы можете захотеть прервать короткое замыкание в случае успеха и продолжить неудачу. Представьте, например, что вы разрешаете пользователю выполнить действие над сообщением в блоге. Вы можете сначала проверить, является ли он суперпользователем, а если нет, вы можете проверить, является ли он автором сообщения или, по крайней мере, соавтором, прежде чем в конечном итоге сообщить, что они не авторизованы.
Это работает, но было бы неплохо устранить некоторую вложенность. В связи с этим было бы неплохо сделать проверки как можно более независимыми и компонуемыми, чтобы упростить сопровождение кода. Мы можем начать с написания каждой проверки в отдельной функции следующим образом:
Большой. Теперь давайте объединим проверки в последовательность:
Теперь нам просто нужен способ объединить эти результаты. В этом случае мы хотим получить первый успешный результат и, если не найдем, вернуть NotAuthorized
. Давайте напишем комбинированную функцию, которая выражает это:
Теперь мы можем передать нашу последовательность в combineResults
, чтобы получить общий результат:
Самое замечательное в том, чтобы сделать это с seq
, состоит в том, что он будет генерировать значения только по мере необходимости, потенциально экономя вам некоторые поиски в базе данных или дорогостоящие вычисления, если предыдущая проверка завершилась успешно.
Если ничего не помогает, примите вложение
Наконец, одно предостережение. Устранение гнездования - не самоцель. Вы должны использовать методы, подобные перечисленным выше, когда они помогают с удобочитаемостью и обслуживанием кода. Несколько совпадений вложенных шаблонов - это нормально, и они могут быть самыми простыми и удобными для новичков.
Выражения и статическая типизация здесь ваши друзья: компилятор обеспечит совпадение всех ваших типов.
Этот пост является частью Адвент-календаря F # 2018