Does Haskell need to write generics in a recursive way?

In most examples, for generic Haskell machines, small bits of computation by type / constructor :+:and are recursively performed :*:. It seems I am solving a problem when this might not work.

I am trying to write a general validation function that takes any two records that have the same form and checks each field in record A for the validation function defined in record B to return an error record of the same form OR recordA itself.

Example:

-- Some type synonyms for better readability
type Name = Text
type Age = Int
type Email = Text
type GeneralError = Text
type FieldError = Text

-- a polymorphic record to help preserve the shape of various records
data User n a e = User {name :: n, age :: a, email :: e}

-- the incoming value which has been parsed into the correct type
-- but still needs various values to be validated, eg length, format, etc
type UserInput = User Name Age Email

-- specifies the exact errors for each field
type UserError = User [FieldError] [FieldError] [FieldError]

-- specifies how to validate each field. the validator is being passed
-- the complete record along with the specific field to allow
-- validations that depends on the value of another field
type UserValidator = User
                     (UserInput -> Name -> Either ([GeneralError], [FieldError]) Name)
                     (UserInput -> Age -> Either ([GeneralError], [FieldError]) Age)
                     (UserInput -> Email -> Either ([GeneralError], [FieldError]) Email)

let (validationResult :: Either ([GeneralError], UserError) UserInput)
  = genericValidation (i :: UserInput) (v :: UserValidator)

, :*: , , , , Left ([GeneralError], UserError) Right UserInput, Left , .

genericValidation Haskell?

+4
2

, :*:, , , , Left ([GeneralError], UserError) Right UserInput, Left .

Applicative Either ! , , , , , , . , , Either, Applicative.

newtype Validation e a = Validation (Either e a) deriving Functor

instance Semigroup e => Applicative (Validation e) where
    pure = Validation . pure
    Validation (Right f) <*> Validation (Right x) = Validation (Right $ f x)
    Validation (Left e1) <*> Validation (Left e2) = Validation (Left $ e1 <> e2)
    Validation (Left e) <*> _ = Validation (Left e)
    _ <*> Validation (Left e) = Validation (Left e)

, , , Semigroup - , . , Validation Either. Either Writer.

Applicative, . , Validation Monad.


, . : . , Identity.

data UserTemplate f = UserTemplate {
    name :: f Name,
    age :: f Age,
    email :: f Email
}
type User = UserTemplate Identity

newtype: a Validator - , a a, .

newtype Validator e a = Validator { runValidator :: a -> Validation e a }

: HTraversable Traversable, Hask. ( .)

class HFunctor t where
    hmap :: (forall x. f x -> g x) -> t f -> t g
class HFunctor t => HTraversable t where
    htraverse :: Applicative a => (forall x. f x -> Compose a g x) -> t f -> a (t g)
    htraverse f = hsequence . hmap f
    hsequence :: Applicative a => t (Compose a g) -> a (t g)
    hsequence = htraverse id

HTraversable ? Traversable Classic β„’ Applicative, Validation , . : "" , . HTraversable - , Applicative .

zipWith .

class HZip t where
    hzip :: (forall x. f x -> g x -> h x) -> t f -> t g -> t h

, UserTemplate, . ( HRepresentable - Representable - , .)

instance HFunctor UserTemplate where
    hmap f (UserTemplate n a e) = UserTemplate (f n) (f a) (f e)

instance HTraversable UserTemplate where
    htraverse f (UserTemplate n a e) = UserTemplate <$>
        getCompose (f n) <*>
        getCompose (f a) <*>
        getCompose (f e)

instance HZip UserTemplate where
    hzip f (UserTemplate n1 a1 e1) (UserTemplate n2 a2 e2) = UserTemplate (f n1 n2) (f a1 a2) (f e1 e2)

, , Generic Template Haskell HTraversable HZip , .

, : Validator , HZip Validator , . htraverse , Validation, . . , HZip (, , Generic).

type Validatable t = (HZip t, HTraversable t)
validate :: (Semigroup e, Validatable t) => t (Validator e) -> Validator e (t Identity)
validate t = Validator $ htraverse (Compose . fmap Identity) . hzip val t
    where val v = runValidator v . runIdentity

, User, . Monoid UserError, e .

type UserError e = UserTemplate (Const e)

instance Semigroup e => Semigroup (UserError e) where
   x <> y = hzip (<>) x y

.

type UserValidator = Validator ([GeneralError], UserError [FieldError])

validateEmail :: UserInput -> UserValidator Email
validateEmail i = Validator v
    where v e
            | '@' `elem` toString e = pure e
            | otherwise = Validation $ Left ([], UserTemplate [] [] [FieldError "missing @"])

validateName :: UserInput -> UserValidator Name
validateName = ...
validateAge :: UserInput -> UserValidator Age
validateAge = ...

userValidator :: UserInput -> UserValidator User
userValidator input = validate $ UserTemplate {
    name = validateName input,
    age = validateAge input,
    email = validateEmail input
}

- - .

+4

, , , . " ", , , .

, generics-sop. , . , , n-ary , . , ; n- , .

import Data.Bifunctor (bimap)
import qualified GHC.Generics as GHC
import Generics.SOP
import Control.Applicative.Lift (Errors,runErrors,failure)

data User = User { name :: Name, age :: Age, email :: Email } deriving (Show,GHC.Generic)

instance Generic User -- this generic is from generics-sop

, Errors transformers. , r:

newtype Validator r a = 
    Validator { runValidator :: r -> a -> Errors [FieldError] a } 

Usher , FieldError N- :

newtype Usher res xs a = Usher { getUsher :: res -> NP (K res) xs }

ushers n- Usher . Monoid; .

ushers :: forall r xs res. (IsProductType r xs, Monoid res)
       => Proxy r -> NP (Usher res xs) xs
ushers _ =
    let expand (Fn injection) =
            Usher $ \res -> hexpand (K mempty) (unK (injection (K res)))
    in hliftA expand (injections @xs @(K res))

, generics-sop:

-- combine the individual fields of a list of uniform n-ary-products 
fold_NP :: forall w xs . (Monoid w, SListI xs) => [NP (K w) xs] -> NP (K w) xs
fold_NP = Prelude.foldr (hliftA2 (mapKKK mappend)) (hpure (K mempty))

. , n- ( r):

validate :: forall r xs . IsProductType r xs
         => NP (Validator r) xs -> r -> Either (NP (K [FieldError]) xs) r
validate validators r =
    let validators' = validators    :: NP (Validator r) xs
        rs = hpure (K r)            :: NP (K r) xs -- a copy of the record in each slot
        np = unZ (unSOP (from r))   :: NP I xs -- generic representation of the record
        validated                   :: NP (Errors [FieldError])   xs
        validated = hliftA3 (\(Validator v) (K rec) (I a) -> v rec a) validators' rs np

        ushers' = ushers (Proxy @r) :: NP (Usher [FieldError] xs) xs -- error injectors
        injected                    :: NP (Errors [NP (K [FieldError]) xs]) xs
        injected = hliftA2 (\(Usher usher) errors ->
                                case runErrors errors of
                                    Right a' -> pure a'
                                    Left es -> failure [usher es])
                           ushers'
                           validated
    in bimap fold_NP (to . SOP . Z) . runErrors . hsequence $ injected

, :

main :: IO ()
main = do
   let valfail msg = Validator (\_ _ -> failure [msg])
       validators = valfail "err1" :* valfail "err2" :* valfail "err3" :* Nil 
   print $ validate validators (User "Foo" 40 "boo@bar")
   -- returns Left (K ["err1"] :* (K ["err2"] :* (K ["err3"] :* Nil)))
+2

Source: https://habr.com/ru/post/1683023/


All Articles