Just upgraded to 2.0 and hit with hundreds of errors like:
Property 'chain' does not exist on type 'Either<string, Slide>'
or, more generically:
TS2339: Property 'fold' does not exist on type 'Either<E, T>'.
Property 'fold' does not exist on type 'Left<E>'.
TS2339: Property 'chain' does not exist on type 'Option<T[]>'.
Property 'chain' does not exist on type 'None'.
Is there a migration guide somewhere?
(also seems StrMap is gone?)
It looks like you are supposed to do
import {either} from "fp-ts/lib/Either";
either.map(something, x => y);
This brings me to another question -- how would you do X number of maps in a chain (e.g. something.map(x => y).map(y => z))?
FYI, the main v2 discussion was happening in #823 and you can read it as a kind of migration guide.
Short answer: you're now supposed to use import { pipe } from 'fp-ts/lib/pipeable';
Ah. Thanks. That's what I figured...
TLDR: for the example something.map(x => y).map(y => z), you would write
import F from "fp-ts/lib/YourFavoriteFunctorInstance";
import {pipe} from "fp-ts/lib/pipeable";
const fz = pipe(something,
F.map(x => y),
F.map(y => z));
@dakom you can check out the release notes of v1.19.0, many features of v2 have been backported to v1 so you can experiment / upgrade your code with the new API before switching to [email protected]
TLDR
Since in v2 data types are no more implemented with classes, chainable APIs will be deprecated.
As an alternative, a pipe function is provided, along with suitable data-last top level functions (one for each deprecated method).
Example
Before
import * as O from 'fp-ts/lib/Option'
O.some(1)
.map(n => n * 2)
.chain(n => n === 0 ? O.none : O.some(1 / n))
.filter(n => n > 1)
.foldL(() => 'ko', () => 'ok')
After
import * as O from 'fp-ts/lib/Option'
import { pipe } from 'fp-ts/lib/pipeable'
pipe(
O.some(1),
O.map(n => n * 2),
O.chain(n === 0 ? O.none : O.some(1 / n)),
O.filter(n => n > 1),
O.fold(() => 'ko', () => 'ok')
)
also seems StrMap is gone?
Yes (it was implemented with a class), you can use pipe + the Record module
Thanks everyone! One thing I'm a bit confused about from skimming the discussions in the issues (admittedly, not really reading closely - just scrolling by) is whether or not there is support for generic map/chain/etc. for all functors and monads?
In other words is it possible to do:
pipe(
O.some(1),
map(n => n * 2),
chain(n === 0 ? O.none : O.some(1 / n)),
)
without prefixing map/chain with O (or, E for eithers) ?
@dakom Nope, you still need to pass correct implementation
I created an upgrade guide over here: https://github.com/gcanti/fp-ts/pull/905
The idea would be to add answers to questions like yours to this guide, should there be more in the future. Add more issues or, if you found the answer, create a PR for that guide.
Cheers!
Cool, thanks!
Just one question at the moment, which I think makes sense to keep here since it speaks to the example already posted...
I was actually using pipe fairly often via import {pipe} from "fp-ts/lib/function"
Though I used it similar-ish to Sanctuary's pipe in that it expected a second arg for the thing to pass in and kick it off, e.g. more like:
pipe(
O.map(n => n * 2),
O.chain(n => n === 0 ? O.none : O.some(1 / n)),
O.filter(n => n > 1),
O.fold(() => 'ko', () => 'ok')
) (O.some(1))
where that O.some(1)) is passed in as the second arg.
I see this now fails... am I doing something wrong? Seems odd to me that pipe() wouldn't accept a second arg so that it can be partially applied, and also that the first arg is not a function....
The name pipe is now reserved to pipeable's pipe, the old pipe from fp-ts/lib/function (i.e. function composition) is replaced by flow
import * as O from 'fp-ts/lib/Option'
import { flow } from 'fp-ts/lib/function'
flow(
O.map((n: number) => n * 2),
O.chain(n => (n === 0 ? O.none : O.some(1 / n))),
O.filter(n => n > 1),
O.fold(() => 'ko', () => 'ok')
)(O.some(1))
Ahh... okay... conceptually, why would I prefer one vs the other (e.g. why did you choose pipe() instead of flow() for the example above)?
Seems like type inference is failing with this new API ... am I doing something wrong here too?
It doesn't know that xs is an Array<string>
import * as O from 'fp-ts/lib/Option';
import {Option} from 'fp-ts/lib/Option';
let foo:Option<Array<string>> = O.some(["hello", "world"]);
let bar = O.chain(xs => O.some(xs.concat(["!"]))) (foo);
I believe this is due to TypeScript not being able to infer the type with data last functions. What always works is to use pipe, even for such simple cases:
pipe(foo, O.chain(…))
that does fix it... but, ouch. My main concern is that this feels a bit unnecessary, will it change in another week or too?
Or should I just accept that JS/TS requires hacks to get compile-time checks - so using pipe() everywhere will keep being a requirement, and isn't so bad...
actually, nm - I can also just give xs an explicit type and the compiler DOES catch me if I give it the wrong type. All good :)
From my perspective feel free to close this issue - if I have more doc/migration questions I'll reference the PR mentioned above. Thanks again!
Sorry one more small thing... what terminology should I use when communicating this to other people? e.g. I want to file a ticket for a project that says something like:
"Switching from [style-a] to [style-b]" ... maybe style-a is "fluent" or "chaining" - and style-b is literally "functional" ?
(won't be just fp-ts, also changing usage of Fluture from being like myFuture.map(fn) to F.map(fn) (myFuture))
Yes, you can always manually specify the types, but it might not be convenient. As soon as you have a function that can provide the required context, the generic type can be inferred. This works with pipe, but also, I think, in a situation like myPromise.then(O.map(x => x + 1)).
Regarding the terminology: "chainable" has been used a lot in https://github.com/gcanti/fp-ts/issues/823, but I'm also not quite sure about the new style. Data last curried functions? Static Land calls these just "static functions".
In v1 I was a heavy user of curry. What's the best upgrade path for that? I couldn't tell by searching issues, PRs, commits or docs.
@jaycle curry (and compose as well) have been deprecated because they might lead to a non typesafe solution. See https://github.com/microsoft/TypeScript/issues/5453#issuecomment-478301595.
The best (and only, I guess) option for currying is to _manually_ curry your functions. By expliciting the types, the curried function type signature could not be wrong.
If anyone here is familiar with writing codemods, it would be amazing if we could write a codemod to convert from v1's methods to v2's pipeable operators.
ts-morph would make this possible. E.g. find all values with type Option, convert methods to functions inside pipe, add imports, etc.
Re. codemod, RxJS had a very similar problem (transitioning from methods to pipeable operators) and they wrote a TSLint rule ("auto fix" enabled) for it: https://github.com/ReactiveX/rxjs-tslint.
It wouldn't be too hard to modify this to work with Option instead of Observable types.
@OliverJAsh That would be great but in practise we've already migrated several medium-to-large size applications to fp-ts@2 by hand. It's not that hard.
Most helpful comment
The name
pipeis now reserved topipeable'spipe, the oldpipefromfp-ts/lib/function(i.e. function composition) is replaced byflow