Недавно я изучал общие концепции Typescript. У меня проблема с пониманием: «Почему функции контравариантны со своими параметрами?». Я знаю это:
Covariance - это ifT extends U (T присваивается U), верно, что G<T> extends G<U> (G<T> также присваивается G<U>)
Contravariance - это если T extends U, то обязательно придет вывод, что G<U> extends G<T> (теперь G<U> можно присвоить G<T>).
Прежде чем задать вопрос на этом форуме, я прочитал следующие сообщения. Однако мне все еще трудно понять, почему функции в Typescript являются контравариантными по своим параметрам:
Я вижу, что не только функции в машинописном тексте контравариантны своим параметрам, но и некоторые другие языки также верны.
Мне так неприятно принять это, не выяснив, почему это правда. Не могли бы вы помочь мне объяснить это, или, если мне не хватает соответствующих знаний, скажите, что это такое.
Я думаю ваша вторая ссылка прекрасно объясняет это на примере того, как работают подтипы функций
@ Берги, я читал это несколько раз и не понимаю, почему Dog -> Dog является супертипом Greyhound → Greyhound с предположением: Greyhound ≼ Dog ≼ Animal.
Это не так. В статье на «вопрос 1» ответ «нет».
прочитав вторую ссылку, я не понимаю, почему Greyhound → Greyhound не является подтипом Dog -> Dog, там не было четкого объяснения или примера, поэтому я не понимаю, хотя я прочитал это много времени и потратил много времени на ее обдумывание.
Предположим, у вас есть переменная let f: Dog → Dog, поэтому вы можете присвоить этой переменной любое значение подтипа Dog → Dog. Теперь вы называете f(dog) (где dog — это Dog). Можете ли вы назначить функцию типа Greyhound → Greyhound функции f, иначе вызов завершится неудачно? Вы не можете, так как это означало бы передачу Dog функции, которая принимает только Greyhound, поэтому Greyhound → Greyhound не является подтипом Dog → Dog.
(Вместо переменной, которой вы присваиваете функцию, в статье используется функция f, которая принимает такую функцию в качестве аргумента - то же самое)
@Bergi, я не понимаю твоего утверждения: «поскольку это означало бы передачу Dog функции, которая принимает только Greyhound». Я не знаю Dog, на который вы ссылаетесь, это Dog тип или dog переменная, потому что вы сказали: передача Dog в функцию, которая принимает только Greyhound
@Bergi, если Greyhound расширить Dog , мы можем передать переменную dog в функцию require param, имеющую тип Greyhound (Greyhound ->Greyhound)
Нет, ты не можешь. Если Greyhound имеет дополнительное свойство по сравнению с Dog (все еще extends Dog), вы не можете использовать Dog, у которого нет этого свойства, в месте, где ожидается Greyhound. (Принимая во внимание, что вы можете использовать экземпляр Greyhound в любом месте, где ожидается Dog).
@ Берги, я неправильно понимаю значение подтипа. после рассмотрения вашего комментария выше, могу ли я заявить, что «подтип — это тип, который заменяет позицию, требующую родительского типа». Когда я присваиваю переменную Greyhound переменной Dog, переменная принимается, потому что Greyhound является Dog, другими словами, Greyhound может играть роль Dog (Greyhound имеет все свойства, которые Dog требуются). Таким образом, он может стоять в положениях, требующих типа Dog.
Да, именно это
@Bergi, когда функция имеет тип Greyhound → Greyhound, присвоенный Dog → Dog. он не принимается, поскольку целевая функция (Dog → Dog) (левая часть присваивания) принимает экземпляры Dog в качестве аргумента. Однако исходная функция Greyhound → Greyhound (правая часть присваивания) не принимает переменную Dog в качестве аргумента. Таким образом, Greyhound → Greyhound не может играть роль Dog → Dog (не заменять Dog → Dog). Следовательно, Greyhound → Greyhound не является подтипом Dog → Dog.
Да, вот и все!
@Bergi, тип возвращаемого значения меняется на тип функции params. Поскольку subtype является supertype, подтип содержит все члены, которые требуются супертипу. Поэтому тип возвращаемого значения должен быть либо Greyhound, либо другой подтип Dog, либо даже Dog.
Да, возвращаемый тип Function является ковариантным.
Спасибо, с вашими объяснениями все понятно, спасибо большое






Я буду использовать конкретный пример Animal и Dog.
class Animal {
move(distanceInMeters: number = 0) {
console.info(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
bark() {
console.info("Woof! Woof!");
}
}
Есть два варианта разрешения присваивания между Dog и Animal: разрешено ли нам заменять Dog на Animal и можем ли мы заменить Animal на Dog.
const animal: Animal = new Dog;
animal.move(10);
Потому что extends означает, что у нас есть участники базы, это нормально. Typescript позволяет это
const dog: Dog = new Animal;
dog.bark();
В bark нет метода Animal, поэтому во время выполнения это не удастся. Машинопись это запрещает.
Теперь давайте посмотрим на типы функций.
const dog: Dog = new Dog;
const animal: Animal = new Animal;
type UseAnimal: (animal: Animal) => void;
type UseDog: (dog: dog) => void;
const useAnimal: UseAnimal = (animal: Animal) => { animal.move(10); }
const useDog: UseDog = (dog: Dog) => { dog.bark() }
Разработчики языка имеют такой же выбор параметров функций. Давайте посмотрим, что произойдет, если мы разрешим два случая
const dogFn: UseDog = useAnimal;
dogFn(dog);
Поскольку мы передаем Dog в useAnimal, все в порядке, оно может move(10), как указано выше. Машинопись позволяет это.
const animalFn: UseAnimal = useDog;
animalFn(animal)
Animal, к которому мы переходим, useDog вызовет сбой во время выполнения, потому что в нем отсутствует bark(). Машинопись это запрещает.
прочитав ваш ответ, я частично понял. Однако у меня все еще есть некоторые неясные моменты, можете ли вы помочь мне их прояснить?
когда я запускаю const animalFn: UseAnimal = useDog;, компилятор выдает мне ошибку: Type 'UseDog' is not assignable to type 'UseAnimal'. Types of parameters 'd' and 'a' are incompatible. Property 'bark' is missing in type 'Animal' but required in type 'Dog'
@LeoPkm2-1 Да, это пример того, что не сработает. const dog: Dog = new Animal; тоже ошибка
Это причина, по которой выдается ошибка T при реализации функции, которая имеет определенный тип, например: animalFn: UseAnimal, если доступ параметров к свойствам или методам, которые не существуют (или не описаны) в типе параметров, является ошибкой. Хотя Dog можно присвоить Animal (собака может перейти в параметр Animal), ожидаемый тип (форма) параметра в функции (animalFn) равен Animal?
Да, вы не можете передать произвольный Animal в useDog, но вы можете передать произвольный Dog в useAnimal
Когда машинописный скрипт видит, что реализация animalFn (это useDog) имеет параметр Dog, имеющий метод, который не существует в ожидаемом типе параметра (Animal). Он сразу же выдает ошибку, чтобы предотвратить ошибку, как я сказал в комментарии выше?
Да, попытка назначить это является ошибкой, потому что в противном случае поведение во время выполнения было бы плохим.
,разумно ли мое объяснение того, почему const animalFn: UseAnimal = useDog; предотвращается в Ts?
или мое объяснение неверно или чего-то не хватает? Пожалуйста, скажите мне, что это такое.
@LeoPkm2-1 Да. но это только половина того, что означает контравариантность. Ts пришлось реализовать проверку, которая принимает const dogFn: UseDog = useAnimal;, типы не совсем совпадают.
Рассмотрев ваш интересный пример, у меня возникли некоторые предположения о том, почему Ц принимает const dogFn: UseDog = useAnimal; Не могли бы вы помочь мне проверить их правильность или неправильность?
Давайте продолжим обсуждение в чате.
«не только […] в машинописном тексте, но и в некоторых других языках […] также» — я бы удивился, если бы существовал какой-нибудь язык, в котором конструктор типа
Functionне является контравариантным в типах параметров?