fp-ts icon indicating copy to clipboard operation
fp-ts copied to clipboard

On the topic of importing

Open hesxenon opened this issue 2 years ago • 5 comments

🚀 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

hesxenon avatar Dec 08 '21 17:12 hesxenon

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?

khusmann avatar Dec 17 '21 18:12 khusmann

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.

enricopolanski avatar Dec 18 '21 14:12 enricopolanski

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 )

hesxenon avatar Dec 19 '21 16:12 hesxenon

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

mikearnaldi avatar Dec 21 '21 10:12 mikearnaldi

yeah, but the Option.t approach is very much possible. As stated this would be my preferred solution anyway.

hesxenon avatar Dec 22 '21 20:12 hesxenon