fp-ts
fp-ts copied to clipboard
On the topic of importing
🚀 Feature request
I think the current way of importing/using parts of fp-ts is a bit awkward and it would be nice to be able to import what you need directly from fp-ts in a capitalized way.
In the tutorials I can find around fp-ts (and in general, I think react made this big?) modules are often imported like
import * as Module from "module"
Notice the capitalized namespace.
Current Behavior
When trying to do this with fp-ts we can either write
import * as Option from "fp-ts/Option"
which forces the user to manually import each module. Or we can just write option
and let autocomplete do
import { option } from "fp-ts"
But it's a bit weird to define something like type x = option.Option<number>
.
Desired Behavior
What I (and hopefully others) really want is
import { Option } from "fp-ts"
type x = Option<number>
const getNumber = Option.getOrElse(0)
and so on.
Suggested Solution
Well, just capitalize all the imports and exports in index.ts
. I don't get why you chose to use capitalized filenames and then you lowercased the imports in the first place :)
Or, if you do not want a breaking change (even though I've never seen anybody actually import directly from "fp-ts" in my vast 2 month experience with fp-ts) you could of course just duplicate all imports and exports.
Who does this impact? Who is this for?
All users, but in a big part beginners I think, because in my experience people have come to expect that "public" api is exported from the top level. There are - as you probably know - even eslint rules to disallow "sub-package" imports.
Describe alternatives you've considered
- Aliasing the imports everywhere.
- writing an "augmentation file" that just imports and re-exports accordingly
Additional context
Since there is already an open issue that suggests default-exporting the main type of a module - with sensible arguments against it - I'd be more than happy if instead of Option
being a type and a namespace (given import { Option } from "fp-ts"
) it's still just a namespace that exports an additional type t
. Seems to be convention in ML world?
But for the sake of all that is holy, make t
an alias to option
, otherwise you'll end up with type info like t<t<string>, t<t<t<number>>>>
because of type expansion.
Your environment
Software | Version(s) |
---|---|
fp-ts | 2.11.5 |
TypeScript | 4.5.2 |
Newbie here. @hesxenon what you describe as "desired behavior" is how I naively expected the imports to work.
Until I found this issue I was pretty confused trying to follow the blog documentation (thanks @hesxenon) it looked like it was recently updated, but since then import { option } from fp-ts/Option
was deprecated so it threw me for a loop.
What about:
import { option as Option } from "fp-ts"
? It still means we have to do the undesirable Option.Option, but aesthetically I prefer that to option.Option. (but am newb so my style sense doesn't count for much)
But yeah, speaking as a newbie, @hesxenon's desired behavior would have "clicked" for me right out of the box:
import { Option } from "fp-ts"
type x = Option<number>
const getNumber = Option.getOrElse(0)
Is there some advantage to Option.Option/option.Option?
import { Option } from "fp-ts"
I find this confusing, am I importing the Option
type or the Option
namespace?
import { option as Option } from "fp-ts"
This is clearly a namespace.
In the end I think that current module pattern is the best compromise we can have and it's in the spirit of exporting multiple modules.
In fact, if anything, I would discourage to import directly from fp-ts
, without specifying the module, I'm not sure there's many benefits.
am I importing the Option type or the Option namespace
That's why I suggested the .t
approach. Otherwise your example probably should be made even more explicit with import type { Option } from "fp-ts/Option"
. Also you never know from a plain import { X } from "x"
whether it's a type or a (canonically capitalized) class. This is exactly why I like the ML approach of lowercasing types and uppercasing modules. You always know what you're dealing with, if it's lowercased it's a type or value (or even both in which case the usage clearly determines it) and if it's uppercased it's a module.
the current module pattern
I guess this is where the confusion comes from... what is the current module pattern? import * as Option from "fp-ts/Option"
? import { option as Option } from "fp-ts"
?
it's in the spirit of exporting multiple modules
With the above in mind, what's not in the spirit of exporting multiple modules with the suggested approach?
I think it's often good to be explicit about imports, but to make the user import every module from the given subspace strongly reminds me of the whole "avoid * imports" discussion. Kevlin Henney has a really interesting talk about this (think it's from NDC conferences) that I'd like to link here if youtube wasn't down right now :/
Anyway I've gone ahead and created such an "aliasing" package with the original copyright license under @hesxenon/fp-ts
(@khusmann )
There are other issues discussing the same thing, the desired behaviour isn't possible, in your snippet Option is both a type and a namespace re-export and unfortunately ts gets confused in that case
yeah, but the Option.t
approach is very much possible. As stated this would be my preferred solution anyway.