import * as O instead of import { Option etc.
Why is it recommended?
Are there any recommendations at all though? I'd be curious to hear what others prefer, especially when working on big codebases.
In my experience, importing functions separately quickly turns into mess and one has to either rename them in import statements (import { map as mapOption } ..., which is a mess in itself) or just use namespace imports.
import * as O is an approach I often see in open-source code in fp-ts ecosystem, but I personally don't find it very nice or readable (especially when you run out of initial letters you can use without name conflicts too), and it doesn't scale for domain model ADTs and their operations defined in the application code.
So far, importing the whole module with its full name (as in import * as Option from 'fp-ts/lib/Option) is the approach I've found to be the most convenient, with the only drawback of having to write things like Option.Option<A> in function signatures. Technically, I could name the imported namespace a bit different (like OptionOps or whatever), but then I'd end up with two import statements for a single module, which is not ergonomic.
In https://github.com/gcanti/fp-ts/issues/1164#issuecomment-599491321, I suggested to add export type T = Option aliases to the modules (and that's what I'm actually using for my custom ADTs), akin to type t pattern in OCaml. It doesn't actually, like, solve the issue, but at least Option.T looks better than Option.Option. WDYT?
/cc @gcanti @raveclassic @mikearnaldi
@aqrln I use the following:
// types are autoimported by name directly from the corresponding module
// since they don't usually overlap
import { Either } from 'fp-ts/lib/Either';
// modules with all their content are autoimported by name from fp-ts index
// this allows to avoid messing with named imports of top-level functions by hand
import { either, option, record } from 'fp-ts';
// pipe, identity and other helpers are autoimported directly by name
import { pipe } from 'fp-ts/lib/pipeable';
export const foo = (r: Record<string, number>): Either<Error, string> =>
pipe(
record.lookup('the key', r),
option.map(v => `value: ${v}`),
either.fromOption,
);
Only autoimports, so I never write imports by hand.
@raveclassic Oh. I totally forgot about fp-ts index, and I never considered importing from both index and separate modules in this way. Thanks a lot, that really settles the question for me.
BTW somebody mentioned in the issues here (don't remember who, when and where :)) that webpack5 and rollup are capable of doing tree-shaking for imports from fp-ts index ;)
Yeah, that makes sense since there's "module": "es6/index.js" field in package.json. On the other hand, this probably implies that one should be careful when importing from both index and lib, since that might result in duplicated code from lib and es6 being present in the bundle, unless one is only importing types from lib. Importing directly from es6 would be safe for both types and functions.
Yep, you should pick suggestion from IDE correctly (either it's */lib/* or */es6/*) and that's the most annoying thing.
I know this issue talks about just updating the documentation, but I've been asked to write here too.
On #1227 I suggested to change the import philosophy entirely:
import { pipe, flow, Either } from 'fp-ts';
// general name for proofs (I'm an fp-noob, so maybe there's a better name)
Either.proof
// Either is both the module and the type
const either: Either = Either.right(5);
Naming types and modules with the same name (Either) makes it easier to import both.
Proofs should have a generic name to be less awkward (compare either.either with either.proof).
Please see the original issue for more information.
Either is both the module and the type
So what is the type then?
@raveclassic The type is Either, as in the example provided.
You can do that on src/Either.ts:
export const Either = { map, chain, getOrElse /* ... */ }
export type Either<E, A> = Left<E> | Right<A>;
and on index.ts:
export { Either } from './src/Either';
export * from './src/function';
export * from './src/pipeable';
and somewhere on your project:
import { Either, pipe, increment} from 'fp-ts';
const either: Either<Error, number> = pipe(
nullableValue,
Either.fromNullable(new Error('Does not exist')),
Either.map(increment),
);
@SRachamim that's interesting, but I wonder how your proposal behaves with respect to tree-shakeablity, did you try?
@gcanti I don't now about tree-shaking, but:
1) If it doesn't tree-shaking friendly, and you want tree-shakibility, then just don't import from fp-ts but from fp-ts/lib/Either. On the other hand, if you prefer ergonomics and don't care about tree-shaking - then use the comfortable index import. It's up to you.
2) As a developer in the JavaScript community, I don't think that libraries and developers should make import-yoga in order to support tree-shaking tools. This is the job of the tree-shaking tools to support tree-shaking with any import approach.
3) I assume the typical fp-ts developer will prefer ergonomic over tree-shaking support, as most of us don't just import it once in while, but we usually use it heavily on all files in the program. Even if we're in a context of a web application. What are the chances that in a single route we won't use almost all the joy of fp-ts? IMHO, the race to tree-shakibility is overrated.
Most helpful comment
@aqrln I use the following:
Only autoimports, so I never write imports by hand.