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

Alias for exported objects of same name as type

Open christianbradley opened this issue 5 years ago • 11 comments
trafficstars

The following is a bit tedious, if not confusing to read:

import { option, array } from 'fp-ts'
const f = array.array.sequence(option.option)

Adding something like the following (to each applicable type):

// fp-ts/lib/Option.ts
export { option as t }

// fp-ts/lib/Array.ts
export { array as t }

Enables it to be less so

import { option, array } from 'fp-ts'
const f = array.t.sequence(option.t)

christianbradley avatar Mar 16 '20 11:03 christianbradley

On a side note, I was recently thinking about OCaml-style type T exports in each module to be nice to have:

import * as Option from 'fp-ts/lib/Option';

function f<A>(x: Option.T<A>): Option.T<B> {
  return pipe(
    g(x),
    Option.chain(h),
    Option.map(u)
  );
}

aqrln avatar Mar 16 '20 11:03 aqrln

Internally, I've basically been just writing wrappers for projects using the type/namespace overlap hack:

// lib/fp-ts/Option.ts
import { Option as T, option as t, some, none, map as mapC, chain as chainC } from 'fp-ts/lib/Option'
export type Option<A> = T<A>
export const Option = { ...t, some, none, mapC, chainC }

// sketch.ts
import { Option } from '../fp-ts/Option'

function f<A>(x: Option<A>): Option<B> {
  return pipe(
    g(x),
    Option.chainC(h),
    Option.mapC(u)
  );
}

christianbradley avatar Mar 16 '20 22:03 christianbradley

Regarding original issue - we just need a PipeableTraversable and export curried sequence/traverse as top-level from array :) /cc @gcanti

raveclassic avatar Mar 17 '20 10:03 raveclassic

Here's a quick and easy solution, using Option as an example. Adding the following two lines for each type would be incredibly helpful for productivity:

export { Option } as T
export { option as t }

Contrived usage example:

import * as Option from 'fp-ts/lib/Option'

type MaybeString = Option.T<string>
// vs 
// type MaybeString = Option.Option<string>

function upcase(s: string) {
  return s.toUpperCase()
}

function upcaseMaybeString(ma: MaybeString) {
  return Option.t.map(ma, upcase);
// vs
// return Option.option.map(
}

christianbradley avatar Apr 26 '20 22:04 christianbradley

@raveclassic @steida - confused?

This just adds a t (representing type) alias to the typeclass (or what I've been calling the typeclass constant) for each type definition (ie: option in Option.ts, either in Either.ts), as well as a T (capital T) alias to the type/interface.

import { option } from 'fp-ts'

const ma: option.Option<string> = option.option.map(option.some("a"), s => s.toUpperCase())
//vs
const mb: option.T<string> = option.t.map(option.some("b"), s => s.toUpperCase())

@gcanti - any thoughts on this?

christianbradley avatar May 06 '20 12:05 christianbradley

@christianbradley I fail to understand what's the problem with

import { option as O } from 'fp-ts'
import { pipe } from 'fp-ts/lib/pipeable'

const toUpperCase = (s: string): string => s.toUpperCase()

const ma: O.Option<string> = pipe(O.some('a'), O.map(toUpperCase))

The module namespace can be renamed, with an as clause (like in Haskell or PureScript)

gcanti avatar May 06 '20 13:05 gcanti

@christianbradley turns out there's already an even better solution than what I suggested earlier in this thread. See this: https://github.com/gcanti/fp-ts/issues/1185#issuecomment-613334914

aqrln avatar May 06 '20 14:05 aqrln

@gcanti - some issues I have with this style of importing:

  1. Using as, in every file where I import fp-ts types, I have to manually type out the imports. Alternately, using intellisense in VS code, I can just type the word option and it will automatically import it from the root of fp-ts. This is a huge pet peeve for me honestly. I know it seems like a small thing, but doing this n+ times per day over the last few years starts to add up :P

  2. Naming conflicts - implementing your suggestion as a standard, what happens in places where I need to import Option and Ord? Record and Reader and Random? It quickly becomes confusing as to how to standardize naming each of these. I'd rather use the full name + autocompletion.

  3. Readability - an extension of point 2 - is this O an Ord or an Option? a Reader or a Record? Especially in larger files, when tracking down bugs, doing code reviews, diffs, etc where you don't have quick scannable access to the import statements, this makes it more difficult to quickly comprehend the code via context.

This is coming from years of using fp-ts in large codebases on a team of 4+ devs, since the early days of 1.x.

The little things make a big difference, especially for foundational libraries that are included in just about every file in my codebase.

christianbradley avatar May 06 '20 14:05 christianbradley

@christianbradley turns out there's already an even better solution than what I suggested earlier in this thread. See this: #1185 (comment)

Worth looking into, though you still end up with either.either.map and option.option.map (uncurried methods), and, as commented, you have to be careful about importing from but lib and index.

christianbradley avatar May 06 '20 14:05 christianbradley

Another solution may be to look at improving the exports from index... ie: something like the following (not tested, just riffing here)

export { Either, either as tEither } from './Either'
export * as either from './Either'

this would give me

import { Either, tEither, either} from 'fp-ts'

// Use the type definition only
type Result = Either<Error, string>

// Use the either module
const getResult = either.fromPredicate(...)

// Use the either type constant
const upcaseResult = (r: Result) => tEither.map(r, toUpperCase)

christianbradley avatar May 06 '20 14:05 christianbradley

This obviously isn't very high priority, and I don't really have a concrete suggestion, but I just want to chime in and say that I also find it to be a slight papercut when dealing with imports from fp-ts. This may be partly because I don't work on JavaScript projects very often.

Most of the time, I end up doing something like this:

import { Either } from 'fp-ts/Either'
import { * as E } from 'fp-ts/Either'

Which is mostly fine. But, then I sometimes have files where I need TaskEither, Task, Either, and maybe even Option, all in the same file. That's where it gets a bit cumbersome:

import { TaskEither } from 'fp-ts/TaskEither'
import { Task } from 'fp-ts/Task'
import { Either } from 'fp-ts/Either'
import { task as T, taskEither as TE, either as E} from 'fp-ts'

Something along the lines of @christianbradley's suggestion for changing the exports from index might help. Maybe even just exporting the types at the top level, so that we don't have to prefix those with task.Task or an extra import statement like I do, would be pretty helpful. So, in index, maybe have export * as either from './Either' *and* export { Either } from './Either'`.

Thanks for the awesome suite of libraries, by the way.

ragnese avatar Oct 14 '21 12:10 ragnese