Fp-ts: A type inference pattern

Created on 18 May 2017  路  10Comments  路  Source: gcanti/fp-ts

The problem

The current implementation of foldMap has some type inference issues

import * as option from 'fp-ts/lib/Option'
import * as array from 'fp-ts/lib/Array'
import { monoidSum } from 'fp-ts/lib/Monoid'
import { foldMap } from 'fp-ts/lib/Foldable'

const monoid = option.getStaticMonoid(monoidSum)
const as = [1]
// x1 :: Option<{}> // <= {} instead of number
const x1 = foldMap(array, monoid, a => option.some(a), as)

Here TypeScript infers {} instead of number!

The solution

If I curry foldMap, the result is way better

export function foldMap$<F extends HKTS, M>(foldable: StaticFoldable<F>, monoid: StaticMonoid<M>): <A>(f: (a: A) => M, fa: HKT<A>[F]) => M {
  return <A>(f: (a: A) => M, fa: HKT<A>[F]) => foldable.reduce((acc, x: A) => monoid.concat(f(x), acc), monoid.empty(), fa)
}

// x2 :: Option<number>
const x2 = foldMap$(array, monoid)(a => option.some(a), as)

The pattern

After some trials and errors, a clean pattern seems to emerge: whenever in PureScript there is a type constraint, in fp-ts we need a curried function accepting the static dictionaries as first arguments

PureScript

foldMap :: forall f a m. Foldable f, Monoid m => (a -> m) -> f a -> m

TypeScript

function foldMap<F extends HKTS, M>(foldable: StaticFoldable<F>, monoid: StaticMonoid<M>): <A>(f: (a: A) => M, fa: HKT<A>[F]) => M

This is good news: in general porting PureScript libraries to fp-ts is pretty straightforward (with the possible exception of highly polymorphic signatures)

breaking change

All 10 comments

Alas there are many functions in fp-ts which I wrote when I was still learning TypeScript in February/March and before I was aware of this pattern, for example

  • Foldable/toArray
  • Dictionary/fromFoldable
  • Dictionary/toUnfoldable
  • Profunctor/lmap
  • Profunctor/rmap
  • Unfoldable/replicate

I'd like to change the current implementation but it's obviously a breaking change. I see 2 options

  • (1) brutal braking changes releasing v0.3
  • (2) migration path from 0.2 to 0.3 via deprecations

An example of (2) would be to define a curried foldMap$ as above and just deprecate foldMap. New code should use foldMap$ instead of foldMap so it will almost ready for the upgrade to v0.3 (should be a simple replace). Not sure if it worth it though

A Con of (2) is a more confusing code base, a Pro is that it leaves some time to figure out if there will be other breaking changes affecting other parts of fp-ts.

@OliverJAsh @sledorze @danielepolencic @leemhenson sorry to tag you all but being "real world" users I'd love to hear from you what you think

I'm fine with (1). It's still early days and I don't mind the breaking changes.

Also, I'll take the opportunity to ask a question.

If we decide for:

(1) brutal braking changes releasing v0.3

Is there anything like https://github.com/facebook/jscodeshift for Typescript?

Breaking changes is ok with me.

... leaves some time to figure out if there will be other breaking changes affecting other parts of fp-ts.

Maybe leave 0.3 in pre for a while until this is bottomed out though?

Is there anything like https://github.com/facebook/jscodeshift for Typescript?

That would be great, AFAIK there's no such a tool. Perhaps it will be available after the work on prettier and typescript-eslint-parser is settled down

PR here https://github.com/gcanti/fp-ts/pull/93

Maybe leave 0.3 in pre for a while until this is bottomed out though?

I will publish a version in the next channel (npm install fp-ts@next) so we can check that everything is ok before actually releasing

@gcanti breaking changes fine with me too!

Hi all, I put up a branch (dev) with the lib folder committed in, please give it a spin on your codebase / lib and let me know if there are unexpected issues

npm i gcanti/fp-ts#dev

Pre-released v0.3 as fp-ts@next

FYI here's the change log so far

# 0.3.0

- **New Feature**
  - add `StateT` monad transformer, closes #104 (@gcanti)
  - add `Store` comonad, closes #100 (@rilut)
  - add `Last` monoid, closes #99 (@gcanti)
  - add `Id` monad (@gcanti)
  - Array: add extend instance (@gcanti)
  - NonEmptyArray: add comonad instance (@gcanti)
  - `examples` folder
  - `exercises` folder
- **Polish**
  - Tuple: remove StaticFunctor checking (@rilut)
- **Breaking Change** (@gcanti)
  - required typescript version: **2.3.3**
  - drop `Static` prefix in type classes
  - Change contramap signature, closes #32
  - Validation: remove deprecated functions
  - Foldable/toArray
  - Dictionary/fromFoldable
  - Dictionary/toUnfoldable
  - Profunctor/lmap
  - Profunctor/rmap
  - Unfoldable/replicate
  - compositions: renaming and signature changes
    - `getFunctorComposition` -> `getCompositionFunctor`
    - `getApplicativeComposition` -> `getCompositionApplicative`
    - `getFoldableComposition` -> `getCompositionFoldable`
    - `getTraversableComposition` -> `getCompositionTraversable`
  - `OptionT`, `EitherT`, `ReaderT` refactoring
  - drop `IxMonadT`, move `IxIO` to the `examples` folder
  - drop `Trans` module
  - `Free` refactoring
  - drop `rxjs` dependency
  - drop `lib-jsnext` folder
  - make `None` constructor private
  - remove `Pointed` and `Copointed` type classes
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mohsensaremi picture mohsensaremi  路  3Comments

FredericLatour picture FredericLatour  路  3Comments

josete89 picture josete89  路  3Comments

miguelferraro picture miguelferraro  路  3Comments

gcanti picture gcanti  路  3Comments