Fp-ts: Free monads parallelize with lift

Created on 4 Sep 2018  路  3Comments  路  Source: gcanti/fp-ts

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!

All 3 comments

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jollytoad picture jollytoad  路  4Comments

gcanti picture gcanti  路  3Comments

mohsensaremi picture mohsensaremi  路  3Comments

vicrac picture vicrac  路  4Comments

FredericLatour picture FredericLatour  路  3Comments