How to get a class with generic type accept an array of different by same generic types?
It's good that you want to experiment with generics, but this isn't the occasion for it. You say:
The idea ... is to support other items in the future in case I want to expand the app...not just coffee.
But you don't need a generic for that. The only requirement for managing an order is that the order's item be Priceable. Priceable is already a type; you don't need to add a generic type to the mix.
struct Order {
var name: String
var item: Priceable
}
class OrderManager {
private var orders: [Order]
init(orders: [Order]) {
self.orders = orders
}
func add(_ order: Order) {
self.orders.append(order)
}
}
Array of different generics
Jon Skeet's info aside, you might be able to do something like this:
public MyGeneric<T2> ToOtherType<T2>()
{
if (typeof(T2).IsAssignableFrom(typeof(T)))
{
// todo: get the object
return null;
}
else
throw new ArgumentException();
}
new MyGeneric<Dog>().ToOtherType<Animal>(),
new MyInheritedGeneric<Cat>().ToOtherType<Animal>()
Defining an array of differing generic types in TypeScript
This is practically the canonical use case for existential generic types, which are not directly supported in TypeScript (neither are they directly supported in most languages with generics, so it's not a particular shortcoming of TypeScript). There is an open feature request, microsoft/TypeScript#14466, asking for this, but it is not part of the language as of TS4.1.
Generics in TypeScript are "universal", meaning that when I say class Foo<T> {...}
I mean that it works for all possible type parameters T
. That lets the consumer of a Foo<T>
specify the value for T
and do what they want with it, while the provider of Foo<T>
needs to allow for all possibilities.
Heterogeneous collections like the one you are trying to describe require "existential" generics. In some sense you want interface Instruction<exists T> {...}
to mean that there is a type parameter T
for which it works. Meaning that the provider of an Instruction
could specify the value for T
and do what they want with it, while the consumer of an Instruction
needs to allow for all possibilities.
For more information about universally-vs-existentially quantified generics, see this SO question and answer.
While there is no direct support for existentials in TypeScript, there is indirect support. The difference between a universal and an existential has to do with who is looking at the type. If you switch the role of producer and consumer, you get existential-like behavior. This can be accomplished via callbacks. So existentials can be encoded in TypeScript.
Let's look at how we might do it for Instruction
. First, let's define Instruction
as a universal generic, the "standalone" version you mentioned (and I'm removing the JQuery
dependency in this code):
interface Instruction<T> {
promise: Promise<T>,
callback?: (data: T) => void
}
Here's the existential encoding, SomeInstruction
:
type SomeInstruction = <R>(cb: <T>(instruction: Instruction<T>) => R) => R;
A SomeInstruction
is a function that calls a function that accepts an Instruction<T>
for any T
and returns the result. Notice how SomeInstruction
does not itself depend on T
anymore. You might wonder how to get a SomeInstruction
, but this is also fairly straightforward. Let's make a helper function that turns any Instruction<T>
into a SomeInstruction
:
const someInstruction = <T,>(i: Instruction<T>): SomeInstruction => cb => cb(i);
Finally we can make your hetereogeneous collection:
const arr: SomeInstruction[] = [
someInstruction({
promise: Promise.resolve({ foo: 'bar' }),
callback: (data) => console.log(data.foo)
}),
someInstruction({
promise: Promise.resolve({ bar: 'foo' }),
callback: (data) => console.log(data.bar)
})
]
That all type checks, as desired.
Actually using a SomeInstruction
is a bit more involved than using an Instruction<T>
, since it takes a callback. But it's not terrible, and again, allows the T
type parameter to appear in a way that the consumer doesn't know what the actual T
type is and therefore has to treat it as any possible T
:
// writing out T for explicitness here
arr.forEach(someInstruction => someInstruction(<T,>(i: Instruction<T>) => {
i.promise.then(i.callback); // works
}))
// but it is not necessary:
arr.forEach(someInstruction => someInstruction(i => {
i.promise.then(i.callback); // works
}))
Nice.
There are other workarounds, but an existential is what you really want. For completeness, here are some possible workarounds that I will mention but not implement:
give up on type safety and use
any
orunknown
and use type assertions to get back the types you want. Blecch.Use a mapped tuple type where you convert a type like
[T1, T2, T3]
to the corresponding[Instruction<T1>, Instruction<T2>, Instruction<T3>]
type. This won't work withpush()
though, so you'd need to work around that somehow too.Refactor
Instruction
so as not to need/expose generics. Whatever the consumer plans to do with anInstruction<T>
it has to be independent ofT
(e.g., I can writei.promise.then(i.callback)
but I can't do much else, right?), so make anInstruction
generic function which requires a valid promise-and-callback pair to create, and returns something non-generic with whatever functionality you need and that's it. In some sense this is a "stripped-down" existential that does not allow the consumer to access the internal promise-callback pair separately.
Playground link to code
Creating an array to store generic types in Java
Question 1:
Basically, this is forbidden by Java language. This is covered in Java Language Specification for generics.
When you use
ArrayList<Integer>[] pl2 = new ArrayList[10]; // warning
you get the compiler warning, because the following example will compile (generating warning for every line of code):
ArrayList wrongRawArrayList = new ArrayList(); // warning
wrongRawArrayList.add("string1"); // warning
wrongRawArrayList.add("string2"); // warning
pl2[0] = wrongRawArrayList; // warning
but now you array, that supposed to contain ArrayList
of Integer
, contains totally wrong ArrayList
of String
objects.
Question 2:
As it was already answered, declaration of p12
provides you with compile time checking and frees you from using casting when getting items from your ArrayList
.
Slightly modified previous example:
ArrayList<Integer>[] pl2 = new ArrayList[10]; // warning
ArrayList<String> wrongArrayList = new ArrayList<String>(); // OK!
wrongArrayList.add("string1"); // OK!
wrongArrayList.add("string2"); // OK!
pl2[0] = wrongArrayList; // ERROR
Now, since you are using generics, this won't compile.
But if you use
ArrayList[] pl2 = new ArrayList[10];
you will get the same result as in the first example.
Typescript: Using an array of generics to create an array of object with generic in the order of the generic list
You need to create a mapped type from T
where there indexes of the array are the keys, and the values are the class wrapping the entry at that index.
class Class<T extends any[]> {
public readonly parameter: {
[Index in keyof T]: OtherClass<T[Index]>
}
}
Now the rest works just like you expect:
const c = new Class<[string, number]>();
const a1: string = c.parameter[0].something;
const a2: number = c.parameter[1].something;
const c1 = new Class<[number, string, SomeClass, SomeOtherClass]>();
const b1: number = c1.parameter[0].something;
const b2: string = c1.parameter[1].something;
const b3: SomeClass = c1.parameter[2].something;
const b4: SomeOtherClass = c1.parameter[3].something;
Playground
How to get a type with multiple generic types to match the type parameters?
You can do the trick if you delegate type check to the method:
private class TypedMap {
private Map<Class<?>, List<?>> map = new HashMap<>();
public <T> void put(Class<T> key, List<T> value) {
map.put(key, value);
}
@SupressWarnings("unchecked")
public <T> List<T> get(Class<T> clazz) {
return (List<T>) map.get(clazz);
}
}
Wildcard
?
in map declaration does not ensure that keyClass<?>
and valueList<?>
would be of the same type. Methodput()
ensures that. You can not declare map asMap<Class<T>, List<T>>
if your class is not generic - that's why you have to use a method.Get method is unchecked. The cast is safe if entries are added with
put()
method. (There's still a problem with raw types - but this is unavoidable)You can add more methods to TypedMap class, but remember about this restrictions.
public static void main(String[] args) {
TypedMap map = new TypedMap();
List<Cat> cats = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
adder.put(Cat.class, cats);
adder.put(Dog.class, dogs);
adder.put(Cat.class, dogs); // compilation error
}
Typescript array of different generics
The error you are seeing here is related to variance. The two functions format
and sort
use T
in a contravariant position making the type TableColumnConfig
contravariant which reverses assignability. You may also have other properties in TableColumnConfig
in a covariant position making the type ultimately invariant.
You can bypass the error by using methods instead of properties containing functions. As I learned today, methods can be optional too.
export class TableColumnConfig<T> {
format?(val: T): string
sort?(val1: T, val2: T): number
constructor() {}
}
const columns: TableColumnConfig<unknown>[] = [];
// this works fine now
columns.push(new TableColumnConfig<number>());
columns.push(new TableColumnConfig<string>());
This works because variance works differently for methods. See here.
Method types both co-vary and contra-vary with their parameter types
Playground
Typescript array of different generic types
The element type of the array would need to be an "existential type", which we could write in pseudocode as exists T. { first: T, second: (arg: T) => string }
. TypeScript currently does not support existential types natively.
One potential workaround is to encode existential types using closures as explained in this answer. If you don't want to use real closures at runtime, you can use a utility library that provides a type definition for existential types (based on an encoding of type functions using indexed access types) and functions to produce and consume existential types that perform type casts but are just the identity at runtime:
// Library
// (Based in part on https://bitbucket.org/espalier-spreadsheet/espalier/src/b9fef3fd739d42cacd479e50f20cb4ab7078d534/src/lib/type-funcs.ts?at=master&fileviewer=file-view-default#type-funcs.ts-23
// with inspiration from https://github.com/gcanti/fp-ts/blob/master/HKT.md)
const INVARIANT_MARKER = Symbol();
type Invariant<T> = {
[INVARIANT_MARKER](t: T): T
};
interface TypeFuncs<C, X> {}
const FUN_MARKER = Symbol();
type Fun<K extends keyof TypeFuncs<{}, {}>, C> = Invariant<[typeof FUN_MARKER, K, C]>;
const BAD_APP_MARKER = Symbol();
type BadApp<F, X> = Invariant<[typeof BAD_APP_MARKER, F, X]>;
type App<F, X> = [F] extends [Fun<infer K, infer C>] ? TypeFuncs<C, X>[K] : BadApp<F, X>;
const EX_MARKER = Symbol();
type Ex<F> = Invariant<[typeof EX_MARKER, F]>;
function makeEx<F, X>(val: App<F, X>): Ex<F> {
return <any>val;
}
function enterEx<F, R>(exVal: Ex<F>, cb: <X>(val: App<F, X>) => R): R {
return cb(<any>exVal);
}
// Use case
const F_FirstAndSecond = Symbol();
type F_FirstAndSecond = Fun<typeof F_FirstAndSecond, never>;
interface TypeFuncs<C, X> {
[F_FirstAndSecond]: { first: X, second: (arg: X) => string };
}
let myArray: Ex<F_FirstAndSecond>[];
myArray.push(makeEx<F_FirstAndSecond, number>({ first: 42, second: (x) => x.toString(10) }));
myArray.push(makeEx<F_FirstAndSecond, {x: string}>({ first: {x: "hi"}, second: (x) => x.x }));
for (let el of myArray) {
enterEx(el, (val) => console.log(val.second(val.first)));
}
(If there's enough interest, I may properly publish this library...)
Related Topics
Dynamic Links Firebase Function Not Being Called at All Xcode 12
Rlmexception', Reason: 'Attempting to Modify Object Outside of a Write Transaction
The "Funk" Sound When Hitting Escape Key in App
How to Fill Triangle in Swift Using Core Graphics
Need Clarification on Typealias Syntax in Swift
Reduce Float Precision Using Regexp in Swift
Appdelegate#Applicationdidfinishlaunching Not Called for Swift 4 Macos App Built from Command Line
Cannot Authenticate User for Aws Appsync with Swift Sdk
Can Not Get Correct Position of View in Swiftui
Flattened Objects in JSON File to Nested Object Structure in Swift
How to Make First Responder in Swiftui Macos
How to Synchronize Access to a Property That Has Didset
Access Each Header and Controls in The Tableview in Swift
Swiftui New App Lifecycle How to Connect The Facebook Sdk
Biometric Authentication Evaluation with Swiftui
<Unknown>:0: Error: Opening Import File for Module 'swift': Not a Directory