func fetchAndSave<M<_>: MonadReader<M, Database> & MonadReader<M, HttpClient>
    & MonadTask<M> & MonadError<M, Error>>(userId: Int) -> M<()> {
  #monadic {
    api: HttpClient <- M.ask()
    db: Database <- M.ask()
    
    user <- api.fetch(userId: userId)
    db.save(user: user)
  }
}

Explanation:

// The above implementation doesn't require separate protocols for requiring
// dependencies like the GAT version. Now we're starting to get into some of the
// benefits of HKTs over GATs.

// The main thing that makes this code pretty verbose is the types. If we wrote 
// this code with the level of type inference Hindley-Milner type inference 
// offers (often found in pure FP languages) we'd be down to:

func fetchAndSave(userId) {
  #monadic {
    api: HttpClient <- ask()
    db: Database <- ask()

    user <- api.fetch(userId: userId)
    db.save(user: user)
  }
}