Все хвалят Gleam за ошибки как значения, и справедливо, Result прекрасен. Но в моделировании данных я сильнее опираюсь на opaque.
Запись pub opaque type Email { Email(String) } означает, что другие модули видят тип, но не могут вызвать конструктор. Получить Email можно только через то, что экспортирует модуль, например fn parse(raw: String) -> Result(Email, EmailError). Так что Result появляется один раз, на входе, а весь остальной код принимает Email и больше ничего не проверяет.
Без opaque тот же Result на входе остаётся, а гарантии дальше нет, потому что кто угодно напишет Email("lol") и обойдёт проверку. Значение ошибки говорит, что проверка не прошла. Opaque-тип говорит, что проверка была.
Elm приходит к тому же, просто не экспортируя конструктор из модуля, та же идея с меньшими церемониями. Мне нравится, что в Gleam это написано прямо на типе, там, где читаешь определение.