Skip to content

Support placeholders and selectable-overloads #173

Closed
@ikatyang

Description

@ikatyang

Ideas

If TS cannot select the correct signature, why don't we select it ourselves?

If types are too hard to build manually, why don't we generate it programmatically?

Types

consider the following types for R.adjust(), it looks ugly but works perfectly in my testing.

type PH = {'@@functional/placeholder': true};
declare const __: PH;

// adjust.d.ts
type adjust = adjust_000;
type adjust_000 = {
  <T, U>(fn: (v: T) => U, index: number, array: T[]): adjust_111<T, U>;
  <T, U>(fn: (v: T) => U, _index: PH, array: T[]): adjust_101<T, U>;
  <T>(_fn: PH, index: number, array: T[]): adjust_011<T>;
  <T>(_fn: PH, _index: PH, array: T[]): adjust_001<T>;
  <T, U>(fn: (v: T) => U, index: number): adjust_110<T, U>;
  (_fn: PH, index: number): adjust_010;
  <X extends "111">(): <T, U>(fn: (v: T) => U, index: number, array: T[]) => adjust_111<T, U>;
  <X extends "101">(): <T, U>(fn: (v: T) => U, _index: PH, array: T[]) => adjust_101<T, U>;
  <X extends "011">(): <T>(_fn: PH, index: number, array: T[]) => adjust_011<T>;
  <X extends "001">(): <T>(_fn: PH, _index: PH, array: T[]) => adjust_001<T>;
  <X extends "11">(): <T, U>(fn: (v: T) => U, index: number) => adjust_110<T, U>;
  <X extends "01">(): (_fn: PH, index: number) => adjust_010;
  <X extends "1">(): <T, U>(fn: (v: T) => U) => adjust_100<T, U>;
  <T, U>(fn: (v: T) => U): adjust_100<T, U>;
};
type adjust_001<T> = {
  <U>(fn: (v: T) => U, index: number): adjust_111<T, U>;
  (_fn: PH, index: number): adjust_011<T>;
  <X extends "11">(): <U>(fn: (v: T) => U, index: number) => adjust_111<T, U>;
  <X extends "01">(): (_fn: PH, index: number) => adjust_011<T>;
  <X extends "1">(): <U>(fn: (v: T) => U) => adjust_101<T, U>;
  <U>(fn: (v: T) => U): adjust_101<T, U>;
};
type adjust_010 = {
  <T, U>(fn: (v: T) => U, array: T[]): adjust_111<T, U>;
  <T>(_fn: PH, array: T[]): adjust_011<T>;
  <X extends "11">(): <T, U>(fn: (v: T) => U, array: T[]) => adjust_111<T, U>;
  <X extends "01">(): <T>(_fn: PH, array: T[]) => adjust_011<T>;
  <X extends "1">(): <T, U>(fn: (v: T) => U) => adjust_110<T, U>;
  <T, U>(fn: (v: T) => U): adjust_110<T, U>;
};
type adjust_011<T> = {
  <U>(fn: (v: T) => U): adjust_111<T, U>;
};
type adjust_100<T, U> = {
  (index: number, array: T[]): adjust_111<T, U>;
  (_index: PH, array: T[]): adjust_101<T, U>;
  <X extends "11">(): (index: number, array: T[]) => adjust_111<T, U>;
  <X extends "01">(): (_index: PH, array: T[]) => adjust_101<T, U>;
  <X extends "1">(): (index: number) => adjust_110<T, U>;
  (index: number): adjust_110<T, U>;
};
type adjust_101<T, U> = {
  (index: number): adjust_111<T, U>;
};
type adjust_110<T, U> = {
  (array: T[]): adjust_111<T, U>;
};
type adjust_111<T, U> = (T | U)[];

Since curried function can return itself by calling with non-parameter, we can use generic to select one of those overloads.

If we want to choose one of the overloads in the signature, it just works fine. ( the "11" means choosing the one with 2 ("11".length) parameters and both parameter are not placeholder )

ps. I think something like "11" is easily to notice which case to use, but it can be changed into something else.

R.adjust<"11">() //=> <T, U>(fn: (v: T) => U, index: number) => adjust_110<T, U>;

and with placeholder ( the "01" means choosing the one with 2 ("01".length) parameters, the first parameter is a placeholder, and the last parameter is not a placeholder )

R.adjust(R.__, 1)<"01">() //=> <T>(_fn: PH, array: T[]) => adjust_011<T>

If there are many kinds of the function, like R.map() for array, functor, etc., we can create them one by one and mixed them with intersection ( & ):

type map_array = map_array_00;
type map_functor = map_functor_00;
type map = map_array & map_functor;

If TS cannot find the correct signature, we can do it ourselves, it just works fine with good looking.

(R.map as R.map_array)(blah, blah, ...)

Implementation

To implement this types, I wrote a dts DOM library dts-element, the following code will generate the above adjust.d.ts.

import * as dts from 'dts-element';

const name = 'adjust';
const placeholder = new dts.BasicType({name: 'PH'});

const generic_T = new dts.GenericType({name: 'T'});
const generic_U = new dts.GenericType({name: 'U'});

// <T, U>(fn: (v: T) => U, index: number, array: T[]) => (T | U)[]
const function_type = new dts.FunctionType({
  generics: [generic_T, generic_U],
  parameters: [
    new dts.Parameter({
      name: 'fn',
      type: new dts.FunctionType({
        parameters: [
          new dts.Parameter({name: 'v', type: generic_T}),
        ],
        return: generic_U,
      }),
    }),
    new dts.Parameter({name: 'index', type: dts.number_type}),
    new dts.Parameter({name: 'array', type: new dts.ArrayType({owned: generic_T})}),
  ],
  return: new dts.ArrayType({owned: new dts.UnionType({types: [generic_T, generic_U]})}),
});

const types = dts.create_curried_function_types({
  name,
  placeholder,
  type: function_type,
  selectable: true,
});
const document = new dts.Document({children: types});

console.log(document.emit());

I am new to functional programming and ramda, I may not consider something else, please correct me if I am wrong.

I think functional programming is a better way to write code and hope to build a full-supported ramda.d.ts, let me know if you need any help, I am happy to make this types better and better.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions