На пути изучения 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/
Добавить комментарий