Skip to content

Export types?! #561

@ivan-kleshnin

Description

@ivan-kleshnin

Hi Dejan! I'm switching to TypeScript (already using Rambda). Have noticed the following complexities which may be due to my misunderstanding or not. Will appreciate your advices.

Let's take a simple case. I want to implement dropEmpty function on top of Rambda.

In JavaScript

const dropEmpty = R.filter(Boolean)

In TypeScript

Problem-1

There are no exported types so I have to copy-paste Dictionary definition from Rambda sources.

// copy-paste
type Dictionary<T> = Record<string, T>

Is there any reason the following (and not only) is not exported from Rambda?

type IndexedIterator<T, U> = (x: T, i: number) => U;
type Iterator<T, U> = (x: T) => U;
type ObjectIterator<T, U> = (x: T, prop: string, inputObj: Dictionary<T>) => U;
type Ord = number | string | boolean | Date;
type Path = string | readonly (number | string)[];
type Predicate<T> = (x: T) => boolean;
type IndexedPredicate<T> = (x: T, i: number) => boolean;
type ObjectPredicate<T> = (x: T, prop: string, inputObj: Dictionary<T>) => boolean;
type RamdaPath = readonly (number | string)[];

Problem-2

Polymorhic signatures (inherited from Ramda) cause multiple troubles.

export function dropEmpty<T>(input: readonly T[]): readonly T[]
export function dropEmpty<T>(input: Dictionary<T>): Dictionary<T>
export function dropEmpty<T>(input : readonly T[] | Dictionary<T>) : readonly T[] | Dictionary<T> {
  if (input instanceof Array) { // <-- this check is necessary to calm down the TS compiler ... @_@
    return R.filter<T>(Boolean, input) // <-- notice the same
  } else {
    return R.filter<T>(Boolean, input) // <-- code...
  }
}

Without this check:

shot

Do we really need those overloads? I imagine Elm-like API design where there are separate modules for List, Dict etc. would be 10x easier in use (both in client and in library code). It could look like this:

Array.filter(Boolean, input) // <-- when you work with arrays
or
Dict.filter(Boolean, input) // <-- when you work with dicts

Problem-3

Polymorphic overloads are not entirely type-safe. The following code compiles:

export function dropEmpty<T>(input: readonly T[]): readonly T[]
export function dropEmpty<T>(input: Dictionary<T>): Dictionary<T>
export function dropEmpty<T>(input : readonly T[] | Dictionary<T>) : readonly T[] | Dictionary<T> {
  if (input instanceof Array) {
    return {} // <-- swap readonly T[] with Dictionary<T>
  } else {
    return [] // <-- as nothing enforces their match
  } 

Problem-4

Polymorphic overloads kinda force the client-side to use function instead of => (for new functions containing overloads).
There's a way to do the same with arrow functions but it has its own problems. Overload-less API would be function-type agnostic.


To reiterate. I'd suggest to:

  1. Export all types from Rambda along with functions

  2. Consider overload-less design for future versions of the library. By overload-less I mean exlusively overloads related to input data types. Overloads related to currying is a different topic. Going this way, the library would no longer mimic Ramda API design. So it's a had choice between two sets of pros and cons.

If I misunderstand something – pls. tell me. I'm still very new to TypeScript and static typing in general.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions