2

I'm new to Haskell so apologies in advance for the potentially stupid question.

I'd like to build a data structure that is constructed from two http requests in my application.

My first request gets a basic list of users which I could choose to decode to Maybe [User]

 r <- getWith opts "https://www.example.com/users"
 let users = decode $ r ^. responseBody :: Maybe [User]

But if I'd like to enrich my user data by calling a second endpoint for each of the users that respond by doing something like

r2 <- getWth opts "https://www.example.com/users/{userid}/addresses"
let enrichedUser = decode $ r2 ^. responseBody :: Maybe EnrichedUser

I can't quite piece these parts together at the minute. I'm in a do block thats expecting an IO ()

Any help would be appreciated!

eddiec
  • 7,608
  • 5
  • 34
  • 36

1 Answers1

1

I'm assuming that the type of enrichedUser is supposed to be Maybe EnrichedUser and not Maybe [EnrichedUser], right?

If so, after extracting the [User] list from users :: Maybe [User], the problem you're facing is running a monadic action (to fetch the web page) for each User. There's a handy combinator for this in Control.Monad:

mapM :: (Monad m) => (a -> m b) -> ([a] -> m [b])

which can be specialized in your situation to:

mapM :: (User -> IO EnrichedUser) -> ([User] -> IO [EnrichedUser])

This says, if you know how to write a function that takes a User and creates an IO action that will create an EnrichedUser, you can use mapM to turn this into a function that takes a list [User] and creates an IO action to create a whole list [EnrichedUser].

In your application, I imagine the former function would look something like:

enrich :: User -> IO EnrichedUser
enrich u = do
    let opts = ...
    let url = "https://www.example.com/users/" 
              ++ userToUserID u ++ "/addresses"
    r2 <- getWith opts url
    let Just enrichedUser = decode $ r2 ^. responseBody
    return enrichedUser
  where decode = ...

and then you can write (in your IO do-block):

r <- getWith opts "https://www.example.com/users"
let Just users = decode $ r ^. responseBody
enrichedUsers <- mapM enrich users
-- here, enrichedUsers :: [EnrichedUser]
...etc...

I've omitted the Maybe processing here for simplicity. If enriching fails, you probably want to somehow coerce a regular User into a default EnrichedUser anyway, so you'd modify the bottom of the enrich function to read:

let enrichedUser = case decode $ r2 ^. responseBody of
    Nothing -> defaultEnrichment u
    Just e  -> e
return enrichedUser

and everything else would stay the same.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • Having learnt a bit more about monad transformers `MaybeT` has proved to be useful to deal with this problem also. – eddiec Oct 13 '17 at 12:26