Валидатор на Haskell и причем здесь Applicative

от автора

На пути изучения Haskell стоит много абстракций, без понимания которых нет смысла двигаться дальше. Один из таких рубежей — аппликативный функтор.

Разберем пример: напишем простой валидатор и посмотрим каким образом пригодится Applicative.

Определим пользователя:

type Name = String type Email = String  data User = User Name Email     deriving (Show)

Попробуем стандартный Either

Функции валидации:

validateName :: Name -> Either [String] Name validateName "" = Left ["name cannot be empty"] validateName s = pure s  validateEmail :: Email -> Either [String] Email validateEmail "" = Left ["email cannot be empty"] validateEmail s = pure s

Тип Either является и функтором, и аппликативным функтором, воспользуемся этим, чтобы написать функцию которая или создает пользователя или нет.

validateUser :: Name -> Email -> Either [String] User validateUser n e =     User <$> validateName n         <*> validateEmail e  -- >>> validateUser "John" "john@email.com" -- Right (User "John" "john@email.com")  -- >>> validateUser "" "" -- Left ["name cannot be empty"]

Работает, но хотелось бы, чтобы валидатор собирал все ошибки. Вот как должен выглядеть результат последнего примера:

— Left [«name cannot be empty»,»email cannot be empty»]

Новый тип Validator

Either не подходит для нашей задачи. Но он нам пригодится:

newtype Validator a = Validator     { runValidator :: Either [String] a     }

Да, тип ошибки: список строк, подходит для нашей задачи. А как же универсальность?

newtype Validator e a = Validator     { runValidator :: Either e a     }

Сейчас нужно определить экземпляры классов Functor и Applicative для валидатора. Нас устроит реализация Functor для Either:

instance Functor (Validator e) where     fmap f = Validator . (f <$>) . runValidator

Нам нужно будет объединять ошибки для этого подойдет (<>) из класса типов Semigroup. Учтем это:

instance Semigroup e => Applicative (Validator e) where     pure = Validator . pure     Validator (Left e1) <*> Validator (Left e2) =         Validator $ Left $ e1 <> e2     Validator eF <*> Validator eA =         Validator $ ($) <$> eF <*> eA

Пригодится вспомогательная функция:

throwError :: e -> Validator e a throwError = Validator . Left

Перепишем функции валидации:

validateName :: Name -> Validator [String] Name validateName "" = throwError ["name cannot be empty"] validateName s = pure s  validateEmail :: Email -> Validator [String] Email validateEmail "" = throwError ["email cannot be empty"] validateEmail s = pure s  validateUser :: Name -> Email -> Either [String] User validateUser n e =     runValidator $         User <$> validateName n             <*> validateEmail e  -- >>> validateUser "John" "john@email.com" -- Right (User "John" "john@email.com")  -- >>> validateUser "" "" -- Left ["name cannot be empty","email cannot be empty"]

Такой результат нас устраивает. И обошлись совсем без монад.


ссылка на оригинал статьи https://habr.com/ru/articles/933862/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *