Reputation: 4251
In my SPA, I have a function that needs to:
type UserId = string;
type User = {id: UserId};
type TagType = "NEED_HELP" | "NEED_STORAGE"
type Tag = {
id: string;
type: TagType;
userId: UserId;
}
type TagDraft = Omit<Tag, "id">
// ----
const createTagDraft = ({tagType, user} : {tagType: TagType, userId: UserID}): TagDraft => ({
type: tagType, userId: userId
})
const postTag = (tagDraft) => pipe(
TE.tryCatch(
() => axios.post('https://myTagEndpoint', tagDraft),
(reason) => new Error(`${reason}`),
),
TE.map((resp) => resp.data),
)
I can combine the entire task with
const createTagTask = flow(createTagDraft, postTag)
Now I would like to also clear some client cache that I have for Tags. Since the cache object has nothing to do with the arguments needed for the tag, I would like to provide it separately. I do:
function createTagAndCleanTask(queryCache) {
return flow(
createTagDraft,
postTag,
TE.chainFirstTaskK((flag) =>
T.of(
queryCache.clean("tagCache")
)
)
)
}
// which I call like this
createTagAndCleanTask(queryCache)({tagType: "NEED_HELP", user: bob})
This works, but I wonder if this is not exactly what I could use ReaderTaskEither
for?
Idea 1: I tried to use RTE.fromTaskEither
on createTagTask
, but createTagTask
is a function that returns a TaskEither, not a TaskEither...
Idea 2: I tried to use RTE.fromTaskEither
as a third step in the flow
after postTag
but I don't know how to provide proper typing then and make it aware of a env config object.
My understanding of this article is that I should aim at something like (args) => (env) => body
instead of (env) => (args) => body
for each functions. But I cannot find a way to invert arguments that are provided directly via flow
.
Is there a way that can I rewrite this code so that I can provide env objects like queryCache in a cleaner way?
Upvotes: 3
Views: 153
Reputation: 2539
Reader is (env) => A
, so the deps need to come "last" in the list of arguments. You need to think of your function as (args) => (env) => result
instead of (env) => (args) => result
as you correctly identified. There is a flip
function in fp-ts that can be used to invert the arguments afterwards to simplify passing the env in first (before the args).
Here's an example of what this might look like in your case
// I just made up a type for the query cache
type QueryCache = { clean: (queryKey: string) => void }
// simple function which deals with cleaning the cache as you want
// Note that it's type is equivalent to `Reader<{ queryCache: QueryCache }, void>`
const cleanCache = (deps: { queryCache: QueryCache }) => {
deps.queryCache.clean("tagCache");
}
const createTagTask = flow(
createTagDraft,
postTag,
// Convert from TaskEither -> ReaderTaskEither here to allow us to compose the below
RTE.fromTaskEither,
// Convert the cleanCache Reader into a ReaderTaskEither
RTE.chain(() => RTE.fromReader(cleanCache))
)
// Example of how you can partially apply the dependencies with flip
declare const deps: { queryCache: QueryCache }
const _createTagTask = flip(createTagTask)(deps);
// And then call the partially applied fn with args as normal
_createTagTask({ tagType: {...}, userId: {...} })
I think you can also replace RTE.chain(() => RTE.fromReader(cleanCache))
with RTE.chainReaderKW(() => cleanCache)
Upvotes: 1