Giter VIP home page Giter VIP logo

Comments (10)

MartinJohns avatar MartinJohns commented on September 27, 2024 3
createStreetLightProposed(["red", "yellow", "green"], "blue"); // Should Error, and there's no <...> way to override

But you can still always do:

const colors: ("red" | "yellow" | "green" | "blue")[] = ["red", "yellow", "green"];
createStreetLightProposed(colors, "blue");

I get the use case to control the public interface to reduce breaking changes when you decide to change the generic arguments, but you can't prevent users from providing their own types.

from typescript.

RyanCavanaugh avatar RyanCavanaugh commented on September 27, 2024 2

TL;DR we're not going to add something we'd immediately ban on DefinitelyTyped

Inference is inference - it is ultimately guided by heuristics that try to answer the question of what the user wanted the type parameter to be, and absolutely can be "wrong" in the sense of not what the user wanted. If your functions' default inference is "good enough" that the inference is always what the user wanted, then you don't need this feature; users don't write type arguments just to help fill up their hard drives. If the default inference isn't good enough - then what? Forcing the user to upcast or downcast because a library author decided they're not allowed to provide manual type arguments is just making a bad inference someone else's problem, and would rightly be called an antipattern.

from typescript.

jcalz avatar jcalz commented on September 27, 2024 1

Please edit to show the actual undesirable case, which looks like .attribute<string, number | string>("age", 33) or attribute<"age", number | string>("age", 33) or something that matches your example.

If I wanted such a thing, I'd probably write something like

public attribute<
  X extends "Please don't manually specify these generic type arguments",
  Name extends string, Type>(name: Name, value: Type) {
  this.attributes.set(name, value);
  return this as Example<Attributes & { [K in Name]: Type }>;
}

which would make any manual specification unlikely to occur unless someone really wanted to (and if they did, they'd probably find some way around any such feature, such as shown in the prev comment).

Playground link

I don't know that the use case shown here is compelling enough for this feature. The only time I've wanted to prevent manual specification of type arguments is when the type parameter has a default for when inference fails (as a workaround for #58977), so you'd get something like

function foo<T = string>(f: () => T = () => ("abc" as T)): T {
  return f();
}

console.log(foo(()=>123).toFixed(2)); // "123.00" πŸ‘
console.log(foo().toUpperCase()); // ABC πŸ‘

console.log(foo<number>().toFixed(2)); // RUNTIME ERROR! πŸ‘Ž

Playground link

But I don't know that this is compelling enough either.

from typescript.

sparecycles avatar sparecycles commented on September 27, 2024 1

Speaking on motivation, let me chime in with an extension of the example for NoInfer

function createStreetLight<C extends string>(
  colors: C[],
  defaultColor?: NoInfer<C>,
) {
  // ...
}
createStreetLight(["red", "yellow", "green"], "red");  // OK
createStreetLight(["red", "yellow", "green"], "blue");  // Error

Which allows for

createStreetLight<"red"|"yellow"|"green"|"blue">(["red", "yellow", "green"], "blue"); // OK, but... ugh

With the proposed feature

function createStreetLightProposed(
  colors: (infer C extends string)[],
  defaultColor?: C,
)

createStreetLightProposed(["red", "yellow", "green"], "blue"); // Should Error, and there's no <...> way to override

The parameters could still be typecast, ... but this is arguably more natural since the order of the <...> type parameters is no longer an artifact of the API implementation.

type Lights = "red"|"yellow"|"green"|"blue";
createStreetLightProposed(["red", "yellow", "green"] as Lights[], "blue"); // OK

// note that this also works for the existing NoInfer example...
createStreetLight(["red", "yellow", "green"] as Lights[], "blue"); // OK

This feels related to the problem Java solves with ? types. e.g., to deal with the exposed "implementation detail" in the generic type signature.

Interface Collector<T,A,R>

  • Type Parameters:
    T - the type of input elements to the reduction operation
    A - the mutable accumulation type of the reduction operation ⚠️ (often hidden as an implementation detail) ⚠️
    R - the result type of the reduction operation

from typescript.

sparecycles avatar sparecycles commented on September 27, 2024 1

Yep! That's exactly the point that the following as Lights[] example trying to make.

@rentalhost identified a DRY opportunity in the type representation (with maybe a win for locality as well).

I'm actually a little more interested in avoiding <...> for that reason since it encourages types to flow through the parameters themselves rather than function signatures.

from typescript.

MartinJohns avatar MartinJohns commented on September 27, 2024

I don't see the point. You can still change the type by having an explicitly typed variable and pass in the value that way.

const value = 33 as number | string;
foo.attribute("age", value)

from typescript.

rentalhost avatar rentalhost commented on September 27, 2024

Okay, let me try to explain my use case and the limitation that prevents me from achieving my goal. Maybe it's possible today, and you can help me, but I haven't been able to solve it yet.

TypeScript allows the use of typeof to get the type of a parameter, but it always returns a broad type, not the specific type. Or rather, it will return the type corresponding to the parameter, and not the type of the argument. For instance, if I have something like:

function example(value: unknown): typeof value {
  return value;
}

example(123); // type is unknown
example("abc"); // type is unknown

To address this, we can use generics so that TypeScript can determine the type based on the argument:

function example<T>(value: T): T {
  return value;
}

example(123); // type is number 123, specifically
example("abc"); // type is string "abc", specifically

This is my goal. However, to achieve it, I need to declare generics that can be modified by the user. This adds an extra layer of care when determining how my generics will work in the future. But my intention isn't to make my function flexible enough for the user to decide what to do β€” I just want to use this important TypeScript feature internally.

So, I want to propose the possibility of creating 'invisible' generics, intended for local use and automatically determined by TypeScript (as he already does today, however, in a "transparent way", so to speak).

function example<internal T>(value: T): T {
  return value;
}

example(123); // type is number 123, specifically
example("abc"); // type is string "abc", specifically
example<number>(123); // Error: type T is internal

Why is this important?

I don’t want to worry about how I manage my generics internally in future updates, as I'm intentionally keeping them out of the user's control. I just want to rely on what TypeScript infers.

Maybe I'm talking about an "improved typeof", but I understand the importance of how it currently works.

Essentially, I just need the input type because I need to 'pass forward' exactly the type that was received. This is necessary because I’m building a schema builder that works like this:

const User = schema("user")
  .attribute("name", String)
  .attribute("age", Number)

typeof User.getAttributes() is { name: string, age: number }

To achieve this, .attribute() captures the name (as generic Name) and the type (as generic Type), and then returns this retyped to consider the added attributes. Something like return this as Example<T & { [K in Name]: ...<Type> }>.

However, this forces me to use generics where I don't actually want to use them β€” I only need to know what is being passed in and account for that within the function to return a new type based on the user’s input.


function example<infer N extends string>(name> N): Uppercase<N>; // or
function example(name: infer N extends string): Uppercase<N>; // or

// typeof example("abc") === "ABC"

function example<T>(name: infer N extends string, type: T) { ... }

// example("abc", 123) will have generic T = number, N is internally "string" but not accessible to userland. 

from typescript.

jcalz avatar jcalz commented on September 27, 2024

Let's say this feature existed. What would you do when someone writes const x: number = 123; example(x); and the "internal" generic is inferred as number and not 123? If it's okay, then why isn't example<number>(123) okay? If it's not okay, then the feature doesn't help you.

from typescript.

rentalhost avatar rentalhost commented on September 27, 2024

@jcalz the goal is to use the same existing feature as generics, but without exposing it directly to the user, since its use would be exclusively internal.

This approach also helps reduce the complexity of the function signature externally. Instead of seeing a signature like example<number>(value: number) (note how number is redundant here), we would just see example(value: number). In this case, number is inferred from const x: number from your example.

So in your example, the type would indeed be inferred as number, not as 123 (which is not the goal anyway).

The main objective, then, is to simply pass along the type received as an argument without needing to expose the existence of a generic, which serves only that purpose.

While generics can solve this, they add an unnecessary maintenance layer in certain cases. For instance, if I define the order as <Name extends string, Type extends JsonValue>, I wouldn’t be able to change it (whether it’s the order, adding new elements before or in between, or retyping it) without causing a breaking change, even though this flexibility was never intended for the user. Think of it as a private method/property in a class.

Using generics as they are today allows the user to provide the generic type themselves. This prevents me from continuing to maintain my function in the intended way. The function only used generics in this manner because it was the only possible way to solve my problem.

from typescript.

typescript-bot avatar typescript-bot commented on September 27, 2024

This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

from typescript.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.