Skip to content

Generics Aliasing and Partial SpecializationΒ #45419

Open

Description

@ecyrbe

Suggestion

πŸ” Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
generics specialization

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Add Generics aliasing and partial Specialization

πŸ“ƒ Aliasing Example

// In external library

export type Monoid<T> = {
  concat: (x: T, y: T) => T;
  empty: T;
};
export type MonoidFn<T> = (concat: (x: T, y: T) => T, empty: T) => Monoid<T>;

// in user code
const monoid: MonoidFn<~T> = (concat, empty) => ({ concat, empty }); // impossible today, T is undefined

Here we want the monoid function to still be generic and still using the original library generic definition. Using ~T tells the compiler we know that T is undefined, just make an alias.

πŸ“ƒ Partial Specialization Example

// In external library

export type AggregateFn<T, U> =  (map: (item: T) => U) => (array: T[]) => U;

// in user code
const sum: AggregateFn<~T, number> = mapReduceSum<T,number>(); // impossible today, T is undefined

Here we want to specialize AggregateFn and make sum an AggregateFn<T, number>, that is still generic but specialized for numbers. Using ~T tells the compiler we know that T is undefined, so make a partial specialization.

πŸ’» Use Cases

This is almost madatory for decent functional programming without having to redeclare functions of an external library.
Examples should be sufficient to explain a basic use case

Activity

  1. RyanCavanaugh commented on Aug 12, 2021

    @RyanCavanaugh
    Member

    The examples here seem to conflate generic type aliases with generic functions. It's already possible to write these types today:

    export type Monoid<T> = {
      concat: (x: T, y: T) => T;
      empty: T;
    };
    export type MonoidFn = <T>(concat: (x: T, y: T) => T, empty: T) => Monoid<T>;
    
    // in user code
    const monoid: MonoidFn = (concat, empty) => ({ concat, empty }); 

    or

    export type AggregateFn2<U> =  <T>(map: (item: T) => U) => (array: T[]) => U;
    
    // in user code
    const sum: AggregateFn2<number> = mapReduceSum<T,number>();

    It seems like the thing you're looking for is a way to go from a generic type alias and a generic function or vice versa. The examples given are not enough to describe the desired semantics of such an operation, though. For example, what should happen if I write this?

    declare const m: Monoid<~T>;
    const x = m.empty; // What type does x have? What can be done with it?
  2. ecyrbe commented on Aug 14, 2021

    @ecyrbe
    Author

    Hello Ryan,

    In your first example, i don't have the option to change the external library declaration. They declared a generic type, i just want to alias it.

    And in AggregateFn2 you just redeclared the external library. Meaning duplicate code, meaning loosing track of original declaration and causing error in code evolution. I can't use these kind of workarounds.

    To follow your comment what i'm asking is not to just to conflate generic functions and generic aliases. It's much more genral than that. i'm asking for generics aliases like C++ have. Maybe i don't have the same vocabulary as you do, coming from a C++ background (see here. Sorry about any miss interpretations.

    So to clarify things, when i wrote :

    const monoid: MonoidFn<~T> = (concat, empty) => ({ concat, empty });

    I meaned something like this (equivalent of c++ exemple i gave) :

    alias <T>MonoidFn2 = MonoidFn<T>
    const monoid: MonoidFn2 = (concat, empty) => ({ concat, empty });

    And when i wrote :

    const sum: AggregateFn<~T, number> = mapReduceSum<T,number>();

    I meaned this :

    alias <T>AggregateFn2 = AggregateFn<T, number>;
    const sum: AggregateFn2 = mapReduceSum<T,number>();

    To answer your second question, in :

    declare const m: Monoid<~T>; // this is wrong, `~` should only be allowed for aliasing generics. You can't declare a Monoid<~T>.
    const x = m.empty;  // this is invalid.
  3. ecyrbe commented on Dec 29, 2021

    @ecyrbe
    Author

    Ryan Cavanaugh (@RyanCavanaugh) do you steel need more info?

  4. MciprianM commented on Jul 5, 2023

    @MciprianM

    I think I am looking for something similar - generic specialization;

    I would like to have something of the sort:

    function encoder<T>(obj : T) : void {/* impl1 */}
    function encoder<number>(obj : number) : void { /* impl2 */ }
    

    There is a lookup that needs to be done when we call encoder in order to find which method should be called; once this method is found it can be replaced with an uniquely identified method. It would be a code generation tool in the end.
    Without this we either have to resort to type checking, which is inefficient(execution time) because of conditional branches, either we have to write one method for each type that we want to call encoder with, which again is inefficient(coding time).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    In DiscussionNot yet reached consensusSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions