Mixing Either and TaskEither in a pipe in fp-ts - functional-programming

I have the following program that works fine when none of the functions is async.
interface Product {
count: number
pricePerItem: number
}
interface Tax {
tax: number
}
interface Delivery {
delivery: number
}
interface PTD { //ProductTaxDelivery
p: Product
t: number
d: number
}
function getProduct(): Either<Error, Product> {
return E.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): Either<Error, number> {
return E.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): Either<Error, number> {
return E.right(p.count * 0.05)
//or maybe return E.left(Error('some error in delivery happened'))
}
function run(): Either<Error, PTD> {
return pipe(
E.Do,
E.bind('p', getProduct),
E.bind('tax', ({p}) => getTax(p)),
E.bind('delivery', ({p}) => getDelivery(p)),
E.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
E.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
}
)
)
}
main()
The question I'm having is if one of my functions, for example getDelivery() is async, then I'm not sure how to solve it.
Here's what I have tried:
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
TE.bind('delivery', ({p}) => getDelivery(p)),
and many other variations, but all ended up in compiler errors.
The equivalent in imperative style is something like:
const getDelivery = async (p: Product) => {
return await something()
}
const run = async (): PTD => {
const product = getProduct()
const tax = getTax(product)
const delivery = await getDelivery(product)
return {
p: product, t: tax, d: delivery
}
}
What is the correct functional way (that I think involves both Either and TaskEither) using fp-ts?
Update: I also tried to replace Either with TaskEither, E with TE everywhere, but the problem is now a compiler error when I tried to fold in main(). Here's the code that replaces:
function getProduct(): TaskEither<Error, Product> {
return TE.right({ count: 10, pricePerItem: 5 })
}
function getTax(p: Product): TaskEither<Error, number> {
return TE.right(p.pricePerItem * p.count * 0.085)
}
function getDelivery(p: Product): TaskEither<Error, number> {
return TE.right(p.count * 0.05)
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => {
console.log(`error: ${e}`)
},
(it) => {
console.log(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
//doNonFunctional()
}
)
)
}
main()
On line with (e) => {, the compiler error says:
error TS2345: Argument of type '(e: Error) => void' is not assignable to parameter of type '(e: Error) => Task<unknown>'.
Type 'void' is not assignable to type 'Task<unknown>'.
Update 2
OK, so I get the code to compile but no output when the program runs
const printError = (e: Error): T.Task<unknown> => {
console.log(`error: ${e}`)
return () => Promise.resolve()
}
const printPTD = (ptd: PTD): T.Task<unknown> => {
console.log(`ok ${ptd.p.count} ${ptd.p.pricePerItem} ${ptd.t} ${ptd.d}`)
return () => Promise.resolve()
}
function run(): TaskEither<Error, PTD> {
return pipe(
TE.Do,
TE.bind('p', getProduct),
TE.bind('tax', ({ p }) => getTax(p)),
TE.bind('delivery', ({ p }) => getDelivery(p)),
TE.map(({ p, tax, delivery }) => ({ p, t: tax, d: delivery }))
)
}
function main() {
pipe(
run(),
TE.fold(
(e) => printError(e),
(ptd) => printPTD(ptd)
)
)
}
main()

The issue is when you create a Task in main with pipe, you are not actually running anything.
This is how Task is defined:
interface Task<A> {
(): Promise<A>
}
// same as type Task<A> = () => Promise<A>
Because Task is a thunk, you need to call it to actually execute the code.
async function main(): Promise<void> {
await pipe(
// ...
// vv note the call here
)()
}
main()
However, I would do it like this:
const main: T.Task<void> = pipe(/* ... */)
main()
Similarly, run doesn't need to be a function; it can be const run = pipe(/* ... */).
Also, there's a Console module that provides log functions that return an IO (a type for side-effectful actions).
Your code could be written as
import * as Console from 'fp-ts/Console'
import * as E from 'fp-ts/Either'
import * as T from 'fp-ts/Task'
import * as TE from 'fp-ts/TaskEither'
import {pipe} from 'fp-ts/function'
// <A>(a: A) => Task<void>
const taskLog = T.fromIOK(Console.log)
// You can still keep getProduct and getTask synchronous
function getProduct(): E.Either<Error, Product> { /* ... */ }
function getTax(p: Product): E.Either<Error, number> { /* ... */ }
function getDelivery(p: Product): TE.TaskEither<Error, number> { /* ... */ }
const run: TE.TaskEither<Error, PTD> = pipe(
TE.Do,
// See below for what TE.fromEither(K) does
TE.bind('p', TE.fromEitherK(getProduct)),
TE.bind('tax', ({p}) => TE.fromEither(getTax(p))),
TE.bind('delivery', ({p}) => getDelivery(p)),
TE.map(({p, tax, delivery}) => ({p, t: tax, d: delivery}))
)
const main: T.Task<void> = pipe(
run,
TE.fold(
e => taskLog(`error: ${e}`),
it => taskLog(`ok ${it.p.count} ${it.p.pricePerItem} ${it.t} ${it.d}`)
)
)
main().catch(console.error)
TE.fromEither converts an Either into a TaskEither:
export declare const fromEither: NaturalTransformation22<'Either', 'TaskEither'>
// same as
export declare const fromEither: <E, A>(fa: Either<E, A>) => TaskEither<E, A>
TE.fromEitherK is the same as fromEither but for functions:
export declare const fromEitherK: <E, A extends readonly unknown[], B>(f: (...a: A) => Either<E, B>) => (...a: A) => TaskEither<E, B>
You can probably guess by now what T.fromIOK (used for taskLog) does:
export declare const fromIOK: <A, B>(f: (...a: A) => IO<B>) => (...a: A) => Task<B>
Here's a CodeSandbox with the full code.

Related

I cannot understand WHY I cannot change state in Redux slice

I get the array of objects coming from backend, I get it with socket.io-client. Here we go!
//App.js
import Tickers from "./Components/TickersBoard";
import { actions as tickerActions } from "./slices/tickersSlice.js";
const socket = io.connect("http://localhost:4000");
function App() {
const dispatch = useDispatch();
useEffect(() => {
socket.on("connect", () => {
socket.emit("start");
socket.on("ticker", (quotes) => {
dispatch(tickerActions.setTickers(quotes));
});
});
}, [dispatch]);
After dispatching this array goes to Action called setTickers in the slice.
//slice.js
const tickersAdapter = createEntityAdapter();
const initialState = tickersAdapter.getInitialState();
const tickersSlice = createSlice({
name: "tickers",
initialState,
reducers: {
setTickers(state, { payload }) {
payload.forEach((ticker) => {
const tickerName = ticker.ticker;
const {
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
} = ticker;
state.ids.push(tickerName);
const setStatus = () => {
if (ticker.yeild > state.entities[tickerName].yeild) {
return "rising";
} else if (ticker.yeild < state.entities[tickerName].yeild) {
return "falling";
} else return "noChange";
};
state.entities[tickerName] = {
status: setStatus(),
price,
exchange,
change,
change_percent,
dividend,
yeild,
last_trade_time,
};
return state;
});
return state;
},
},
});
But the state doesn't change. I tried to log state at the beginning, it's empty. After that I tried to log payload - it's ok, information is coming to action. I tried even to do so:
setTickers(state, { payload }) {
state = "debag";
console.log(state);
and I get such a stack of logs in console:
debug
debug
debug
3 debug
2 debug
and so on.

How can I type in flow a high order function that takes a function to generate data?

I want to type using generics a function that simulates a query. To give the most flexibility to the type of data to be used (and to avoid returning same referenced data) this function takes another function to generate the data to return. Because this function can return any data type, I don't want to restrict it to any specific type, but I don't want to use any either.
I thought this could be something achievable with generics, but all my attempts to type it properly fail. Here is what I tried so far:
//#flow
import { useState, useEffect } from 'react'
export const makeUseQuery = <T>(generateData: () => T) => () => {
const [data, setData] = useState()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const id = setTimeout(() => {
setData(generateData())
setIsLoading(false)
}, 2500)
return () => {
clearTimeout(id)
}
}, [])
return {
data,
isLoading,
error: null,
}
}
The error I get from flow is that I can not let the generic escape from the scope, but I am not sure how else can I keep this type safe.
Maybe something like this?
import { useState, useEffect } from 'react'
type UseQueryResult<Response> = $ReadOnly<{|
data: Response | void,
isLoading: boolean,
error: null,
|}>;
export const makeUseQuery = <Response>(
generateData: () => Response
): (() => UseQueryResult<Response>) =>
(): UseQueryResult<Response> => {
const [data, setData] = useState()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const id = setTimeout(() => {
setData(generateData())
setIsLoading(false)
}, 2500)
return () => {
clearTimeout(id)
}
}, [])
return {
data,
isLoading,
error: null,
}
}
(try)

useEffect infinite loop with dependency array on redux dispatch

Running into an infinite loop when I try to dispatch an action which grabs all recent posts from state.
I have tried the following in useEffect dependency array
Object.values(statePosts)
useDeepCompare(statePosts)
passing dispatch
omitting dispatch
omitting statePosts
passing statePosts
doing the same thing in useCallback
a lot of the suggestions came from here
I have verified that data correctly updates in my redux store.
I have no idea why this is still happening
my component
const dispatch = useDispatch()
const { user } = useSelector((state) => state.user)
const { logs: statePosts } = useSelector((state) => state.actionPosts)
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
actionPosts createSlice
const slice = createSlice({
name: 'actionPosts',
initialState: {
posts: [],
},
reducers: {
postsLoading: (state, { payload }) => {
if (state.loading === 'idle') {
state.loading = 'pending'
}
},
postsReceived: (state, { payload }) => {
state.posts = payload
},
},
})
export default slice.reducer
const { postsReceived, postsLoading } = slice.actions
export const getActionPostsRest = (email) => async (dispatch) => {
try {
dispatch(postsLoading())
const { data } = await getUserActionPostsByUser({ email })
dispatch(postsReceived(data.userActionPostsByUser))
return data.userActionPostsByUser
} catch (error) {
throw new Error(error.message)
}
}
Remove dispatch from dependencies.
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
you cannot use hook as dependency and by the way, ref.current, is always undefined here
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
because useDeepCompare essentially is just a function that you initiate (together with ref) on each call, all it does is just returns value. That's where the loop starts.

Basic redux-saga, getting back undefined data

So I'm currently learning Redux-Saga and need a little help.
I've received the action and the watcherSaga has caught it and sent it to the workerSaga which runs a function with axios.get to receive data. In the function, I can actually console.log the data and return it, however when it gets back to the saga, the data is undefined. Here are some screenshots, please let me know if you need any other information.
You need to return await axios.get(API_URL).
E.g.
rootSaga.js:
import { call, put, takeEvery } from 'redux-saga/effects';
import { getBlogsSaga } from './getBlogSaga';
const BLOGS = {
LOAD: 'BLOGS_LOAD',
};
function setBlogs(payload) {
return {
type: 'SET_BLOGS',
payload,
};
}
function* displayBlogs() {
const data = yield call(getBlogsSaga);
console.log(data);
yield put(setBlogs(data));
}
function* rootSaga() {
yield takeEvery(BLOGS.LOAD, displayBlogs);
}
export { rootSaga, displayBlogs };
getBlogSaga.ts:
const getBlogsSaga = async () => {
return await Promise.resolve().then(() => {
return [1, 2, 3];
});
};
export { getBlogsSaga };
rootSaga.test.ts:
import { displayBlogs } from './rootSaga';
import { runSaga } from 'redux-saga';
describe('63000691', () => {
it('should pass', async () => {
const dispatched: any[] = [];
await runSaga(
{
dispatch: (action) => dispatched.push(action),
getState: () => ({}),
},
displayBlogs,
).toPromise();
});
});
test result:
PASS src/stackoverflow/63000691/rootSaga.test.ts
63000691
✓ should pass (16 ms)
console.log
[ 1, 2, 3 ]
at src/stackoverflow/63000691/rootSaga.ts:17:11
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.235 s, estimated 3 s
Your arrow function uses curly braces { so there is no implicit return. Either explicitly return axios.get (and incidentally since you are returning a promise there is no need to use async/await) or change to parens to take advantage of the explicit return.
const getBlogsSaga = async () => {
return await axios.get(..
}
or
const getBlogsSaga = async () => (
await axios.get(...
)

Redux Sagas Recursion Not Working

I want to find a item and its sub items by item id, and I write the following code, but fetchSubItems() always not working, and throwing exception 'TypeError: Cannot read property 'root' of undefined', anyone can help me?
export function *fetchItem(api, id){
const item = yield call (api.getItem, id)
yield put(Actions.addItem(item))
yield call(fetchSubItems, item)
yield put(Actions.success())
}
export function *fetchSubItems(api, item){
if(item.children){
const children = yield item.children.map((id)=>{
return call(api.getItem, id)
})
yield put(Actions.addItems(children))
// the following lines throws 'TypeError: Cannot read property 'root' of undefined'
yield children.map((child)=>{
call(fetchSubItems, api, child)
})
}
}
It seems that a return statement is missing in the last call. The working example:
import Promise from 'bluebird';
import { delay } from 'redux-saga';
import { call } from 'redux-saga/effects';
import {
reducer
} from '../reducers/counter';
import { logger } from '../utils';
const name = '19/Tree_Traversal';
const log = logger(name);
const delayTime = 10;
const tree = {
1: {children: [2, 3]},
2: {children: [4, 5, 6]},
3: {children: []},
4: {children: []},
5: {children: []},
6: {children: [7]},
7: {children: []}
};
const api = {
getItem(id) {
log(`getItem(${id})`);
return delay(delayTime, tree[id]);
}
};
export function *fetchItem(/*api, */id = 1) {
const item = yield call(api.getItem, id);
// yield put(Actions.addItem(item))
yield call(fetchSubItems, /*api, */item);
// yield put(Actions.success())
}
export function *fetchSubItems(/*api, */item) {
if (item.children) {
const children = yield item.children.map((id) => {
return call(api.getItem, id);
});
// yield put(Actions.addItems(children))
yield children.map((child) => {
return call(fetchSubItems, child); // <=== added `return`
});
}
}
export default {
name,
saga: fetchItem,
reducer: reducer,
useThunk: !true,
execute(store) {
return Promise.delay(8 * delayTime)
.then(() => this);
}
};
returns the following log:
00000000: [counter reducer] action Object {type: "##redux/INIT"}
00000003: [Runner] ---------- running example 19/Tree_Traversal
00000004: [Runner] store initial state 0
00000008: [19/Tree_Traversal] getItem(1)
* 00000060: [19/Tree_Traversal] getItem(2)
00000061: [19/Tree_Traversal] getItem(3)
* 00000074: [19/Tree_Traversal] getItem(4)
00000074: [19/Tree_Traversal] getItem(5)
00000075: [19/Tree_Traversal] getItem(6)
* 00000088: [19/Tree_Traversal] getItem(7)
00000091: [Runner] store final state 0
00000092: [Runner] ---------- example 19/Tree_Traversal is done

Resources