-- | This module shows how to 'traverse' over a 'Record', allowing you to
-- perform side-effects for each field and return a transformed value.
--
-- @since 0.0.3.0
module Prairie.Traverse where

import Data.List (foldl')
import Prairie.Class
import Control.Applicative (liftA2)
import Prairie.Fold

-- | Apply an effectful function over each field of a 'Record', producing
-- a new 'Record' in the process.
--
-- This example use increments a @User@s age and requests a new name.
--
-- @
-- happyBirthday :: User -> IO User
-- happyBirthday =
--     traverseRecord
--          (\\val field ->
--              case field of
--                  UserName -> do
--                      putStrLn $ "Current name is: " <> val
--                      putStrLn "Please input a new name: "
--                      getLine
--                  UserAge -> do
--                      putStrLn $ "Current age is " <> show val
--                      pure (val + 1)
--          )
-- @
--
-- If you only want to target a single field, you can use a wildcard match
-- and `pure`. This example also uses @LambdaCase@.
--
-- @
-- nameAtLeastOneCharacter :: User -> 'Maybe' User
-- nameAtLeastOneCharacter =
--      'traverseRecord'
--          (\val ->
--              \case
--                  UserName -> do
--                      'guard' ('length' val >= 1)
--                      'pure' val
--                  _ ->
--                      'pure' val
--          )
-- @
--
-- @since 0.0.3.0
traverseRecord
    :: forall rec f. (Record rec, Applicative f)
    => (forall ty. ty -> Field rec ty -> f ty)
    -> rec
    -> f rec
traverseRecord :: forall rec (f :: Type -> Type).
(Record rec, Applicative f) =>
(forall ty. ty -> Field rec ty -> f ty) -> rec -> f rec
traverseRecord forall ty. ty -> Field rec ty -> f ty
f rec
init =
    (f rec -> SomeFieldWithValue rec -> f rec)
-> f rec -> [SomeFieldWithValue rec] -> f rec
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: Type -> Type) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' f rec -> SomeFieldWithValue rec -> f rec
k (rec -> f rec
forall a. a -> f a
forall (f :: Type -> Type) a. Applicative f => a -> f a
pure rec
init) (rec -> [SomeFieldWithValue rec]
forall rec. Record rec => rec -> [SomeFieldWithValue rec]
recordToFieldList rec
init)
  where
    f' :: Field rec ty -> ty -> f (SomeFieldWithValue rec)
    f' :: forall ty. Field rec ty -> ty -> f (SomeFieldWithValue rec)
f' Field rec ty
field ty
val =
        Field rec ty -> ty -> SomeFieldWithValue rec
forall rec a. Field rec a -> a -> SomeFieldWithValue rec
SomeFieldWithValue Field rec ty
field (ty -> SomeFieldWithValue rec)
-> f ty -> f (SomeFieldWithValue rec)
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
<$> ty -> Field rec ty -> f ty
forall ty. ty -> Field rec ty -> f ty
f ty
val Field rec ty
field
    k :: f rec -> SomeFieldWithValue rec -> f rec
k f rec
frec (SomeFieldWithValue Field rec a
field a
val) =
        (SomeFieldWithValue rec -> rec -> rec)
-> f (SomeFieldWithValue rec) -> f rec -> f rec
forall a b c. (a -> b -> c) -> f a -> f b -> f c
forall (f :: Type -> Type) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2
            (\(SomeFieldWithValue Field rec a
field' a
val') rec
rec -> Field rec a -> a -> rec -> rec
forall rec ty. Record rec => Field rec ty -> ty -> rec -> rec
setRecordField Field rec a
field' a
val' rec
rec)
            (Field rec a -> a -> f (SomeFieldWithValue rec)
forall ty. Field rec ty -> ty -> f (SomeFieldWithValue rec)
f' Field rec a
field a
val)
            f rec
frec