Hi @gcanti
I was playing around with Free monads I have this interesting case:
// algebras.ts
export const write = (path: string) => (toWrite: object) => fp.free.liftF(new Write(path, toWrite, (a: object) => a));
export const read = (filePath: string) => fp.free.liftF(new Read(filePath, a => a));
export const validate = (body: object) => fp.free.liftF(new Validate(body, a => a));
// main.ts
algebras.validate({})
.chain( input => {
return algebras.read('file')
.map( dataFromDisk => { dataFromDisk.data.push(input); return dataFromDisk; });
}).chain(write);
Then I realize I can run in parallel read and validate, then I start to do the following:
const mixResults = (newData: Label, readData: Header): Header => {
readData.data.push(newData);
return readData;
};
const lift = fp.apply.liftA2()
const liftedMixResult = lift(fp.function.curry(mixResults));
liftedMixResult(algebras.validate({}))(algebras.read('file')).chain(write);
But I'm not quite sure what should be in the liftA2, Any help would be appreciated.
Regards!
The simplest way I can think of is to define a specialized liftA2
import { Free, liftF } from 'fp-ts/lib/Free'
import { Curried2 } from 'fp-ts/lib/function'
//
// Algebra
//
const AlgebraF = 'Algebra'
type AlgebraF = typeof AlgebraF
class Write<A> {
readonly _tag = 'Write'
readonly _A!: A
readonly _URI!: AlgebraF
constructor(readonly path: string, readonly content: string, readonly more: A) {}
}
class Read<A> {
readonly _tag = 'Read'
readonly _A!: A
readonly _URI!: AlgebraF
constructor(readonly path: string, readonly more: (s: string) => A) {}
}
class Validate<A> {
readonly _tag = 'Validate'
readonly _A!: A
readonly _URI!: AlgebraF
constructor(readonly body: object, readonly more: (s: string) => A) {}
}
const write = (path: string) => (content: string) => liftF(new Write(path, content, undefined))
const read = (path: string) => liftF(new Read(path, a => a))
const validate = (body: object) => liftF(new Validate(body, a => a))
const mixResults = (data: string) => (input: string): string => data + input
//
// sequenced program
//
const program = validate({})
.chain(input => read('fileA').map(data => mixResults(data)(input)))
.chain(write('fileB'))
//
// parallel program
//
const liftA2Algebra = <A, B, C>(
f: Curried2<A, B, C>
): Curried2<Free<AlgebraF, A>, Free<AlgebraF, B>, Free<AlgebraF, C>> => {
return fa => fb => fb.ap(fa.map(f))
}
const mixResultsLifted = liftA2Algebra(mixResults)
const program2 = mixResultsLifted(validate({}))(read('fileA')).chain(write('fileB'))
Alternatively you could define an instance of Applicative and use the standard liftA2
import { URI, of as freeOf } from 'fp-ts/lib/Free'
import { Applicative2C } from 'fp-ts/lib/Applicative'
import { phantom } from 'fp-ts/lib/function'
import { liftA2 } from 'fp-ts/lib/Apply'
//
// instance of `Applicative` for `FreeAlgebraF<A>`
//
type FreeAlgebraF<A> = Free<AlgebraF, A>
const map = <A, B>(fa: FreeAlgebraF<A>, f: (a: A) => B): FreeAlgebraF<B> => {
return fa.map(f)
}
const of: <A>(a: A) => FreeAlgebraF<A> = freeOf
const ap = <A, B>(fab: FreeAlgebraF<(a: A) => B>, fa: FreeAlgebraF<A>): FreeAlgebraF<B> => {
return fa.ap(fab)
}
// the following module augmentation won't be required
// when https://github.com/gcanti/fp-ts/pull/559 lends
declare module 'fp-ts/lib/HKT' {
interface URI2HKT2<L, A> {
Free: Free<L, A>
}
}
const instance: Applicative2C<URI, AlgebraF> = {
URI,
_L: phantom,
map,
of,
ap
}
const mixResultsLifted2 = liftA2(instance)(mixResults)
FYI the missing module augmentation has been released in v1.8.1