Fp-ts: What's your monad stack?

Created on 14 Oct 2017  路  10Comments  路  Source: gcanti/fp-ts

I wonder if it's worth consolidating a common stack, something like

type ReaderTaskEither<E, L, A> = (e: E) => TaskEither<L, A>
discussion

Most helpful comment

@raveclassic I mean when you have to nest monads, let me show you an example.

Say you have a simple API like this

import { Task } from 'fp-ts/lib/Task'
import axios from 'axios'

/** If successful, returns the invoice id */
const purchase1 = (userId: string, productId: string): Task<string> =>
  new Task(() => axios.post(`https://mysite/api/user/${userId}/purchase/${productId}`, {}).then(res => res.data))

There's no error handling though, so you switch to TaskEither (already consolidated in fp-ts)

type PurchaseError = { type: 'UserNotFound' } | { type: 'ProductNotFound' } | { type: 'PurchaseNotAllowed' }

import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither'

const purchase2 = (userId: string, productId: string): TaskEither<PurchaseError, string> =>
  tryCatch<PurchaseError, string>(
    () => axios.post(`https://mysite/api/user/${userId}/purchase/${productId}`, {}).then(res => res.data),
    reason => {
      // error handling here...
      return { type: 'PurchaseNotAllowed' }
    }
  )

The url is hard-coded though, and here it comes ReaderTaskEither

class ReaderTaskEither<E, L, A> {
  constructor(readonly run: (e: E) => TaskEither<L, A>) {}
  /* ... */
}

type AppConfig = {
  apiRoot: string
}

const purchase3 = (userId: string, productId: string): ReaderTaskEither<AppConfig, PurchaseError, string> =>
  new ReaderTaskEither(config =>
    tryCatch<PurchaseError, string>(
      () => axios.post(`${config.apiRoot}/user/${userId}/purchase/${productId}`, {}).then(res => res.data),
                          // ^ no more hard-coded
      reason => {
        // error handling here...
        return { type: 'PurchaseNotAllowed' }
      }
    )
  )

purchase3('123', '456')
  .run({ apiRoot: 'https://mysite/api' })
  .value.run()
  .then(e => e.fold(error => console.error(error), invoiceId => console.log(invoiceId)))

All 10 comments

I'm only in the middle of integration of fp-ts into our codebase. We are using Option, Either and some custom ADTs right now. The plans are to use Validation also.

@raveclassic I mean when you have to nest monads, let me show you an example.

Say you have a simple API like this

import { Task } from 'fp-ts/lib/Task'
import axios from 'axios'

/** If successful, returns the invoice id */
const purchase1 = (userId: string, productId: string): Task<string> =>
  new Task(() => axios.post(`https://mysite/api/user/${userId}/purchase/${productId}`, {}).then(res => res.data))

There's no error handling though, so you switch to TaskEither (already consolidated in fp-ts)

type PurchaseError = { type: 'UserNotFound' } | { type: 'ProductNotFound' } | { type: 'PurchaseNotAllowed' }

import { TaskEither, tryCatch } from 'fp-ts/lib/TaskEither'

const purchase2 = (userId: string, productId: string): TaskEither<PurchaseError, string> =>
  tryCatch<PurchaseError, string>(
    () => axios.post(`https://mysite/api/user/${userId}/purchase/${productId}`, {}).then(res => res.data),
    reason => {
      // error handling here...
      return { type: 'PurchaseNotAllowed' }
    }
  )

The url is hard-coded though, and here it comes ReaderTaskEither

class ReaderTaskEither<E, L, A> {
  constructor(readonly run: (e: E) => TaskEither<L, A>) {}
  /* ... */
}

type AppConfig = {
  apiRoot: string
}

const purchase3 = (userId: string, productId: string): ReaderTaskEither<AppConfig, PurchaseError, string> =>
  new ReaderTaskEither(config =>
    tryCatch<PurchaseError, string>(
      () => axios.post(`${config.apiRoot}/user/${userId}/purchase/${productId}`, {}).then(res => res.data),
                          // ^ no more hard-coded
      reason => {
        // error handling here...
        return { type: 'PurchaseNotAllowed' }
      }
    )
  )

purchase3('123', '456')
  .run({ apiRoot: 'https://mysite/api' })
  .value.run()
  .then(e => e.fold(error => console.error(error), invoiceId => console.log(invoiceId)))

A reference implementation ReaderTaskEither

That s an interesting question and to be honnest, i ve not introduced yet monad transformers to the team.
I ll do it but i m lurking at the different task/io libraries out there..
I m concerned about correctness, performance and leaks and know there s a lot of efforts in funfix atm.
Also i ve not checked (i m late on it) how well or not fp-ts and funfix integrate with each other (diff hkt impl may be a show stopper)

I m concerned about correctness, performance and leaks and know there s a lot of efforts in funfix atm

@sledorze that's interesting and something I would like to dig deeper into, could you please point me to some resources?

how well or not fp-ts and funfix integrate with each other

Not sure, that's interesting as well. I'll do some experiments and open a new issue

@gcanti you may find some insight on his creator (alex) blog: https://alexn.org/blog/2017/10/11/javascript-promise-leaks-memory.html
For information he his also behind the Monix library in Scala.
Heres the gitter ofthe project: https://gitter.im/funfix/funfix
Basically my beliefs are based on this guy, what he has achieved and his willing to replicate that on node/browser.
And to replicate his words:

alexelcu [3:38 PM] 
I don鈥檛 have numbers yet. `IO` is a port of Monix Task, which is currently the fastest implementation for Scala (versus Cats IO, FS2 and Scalaz 7) and so this `IO` has a really good encoding.

If it鈥檚 not amongst the fastest, then I鈥檒l treat that as a bug that needs to be fixed.

About performance you can get some infos from here (execution strategies):
https://github.com/funfix/funfix/pull/37

That may be a good start.

@gcanti Ok I'm starting to use a stack based on futureEither and readerFutureEither (Future from funfix) - derived from the advices you gave.
It works very well.
Not sure if it could belong to that repo anyway as it would create a non desirable dependency.

It works very well

@sledorze really glad to hear that: I tried to build fp-ts so it can be expanded, as I did with fp-ts-fluture or fp-ts-rxjs, your move to funfix is a good proof of that

Not sure if it could belong to that repo anyway

Maybe you could publish a package

But since, ReaderTaskEither implements Monad3, we can't use it in the MTL example.

Is there any workaround for this? @gcanti

@RPallas92 with more overloadings (alas doing MTL-style in TypeScript is super verbose) see https://github.com/gcanti/fp-ts/pull/410

Was this page helpful?
0 / 5 - 0 ratings

Related issues

steida picture steida  路  4Comments

denistakeda picture denistakeda  路  4Comments

FruitieX picture FruitieX  路  3Comments

mohsensaremi picture mohsensaremi  路  3Comments

josete89 picture josete89  路  3Comments