Забывает ли структурное подтипирование потока определенные свойства подтипа?

В настоящее время я изучаю структурную типизацию. Я скептически отношусь к идее, что два типа считаются эквивалентными только потому, что у них есть часть общей структуры. Это очень похоже на статическую утиную типизацию и полностью игнорирует семантический уровень типов. Поэтому я внимательно изучил структурную типизацию обычных объектов потоком и столкнулся со следующим поведением:

const o:{} = {foo: true};
o.foo; // type error

{} — это структурный тип и надтип всех обычных объектов. Следовательно, имеет смысл аннотировать им o, потому что {foo: true} является структурным подтипом {}. Однако, когда я пытаюсь получить доступ к существующему свойству foo, эта операция не выполняет проверку. Это странно, потому что AFAIK структурный подтип обычно может содержать определенные свойства, если он также включает все необходимые свойства своего супертипа.

Кажется, что алгоритм структурного подтипа потока иногда забывает свойства, характерные для определенного подтипа. Это поведение предназначено, или я просто столкнулся с крайним случаем?


person Community    schedule 13.09.2017    source источник
comment
Не могли бы вы уточнить, что вы имеете в виду? Объявив тип : {}, вы явно стерли информацию о нем, точно так же, как если бы вы сделали Animal foo = new Cat(), foo не знает, что это Cat, он просто знает, что это Animal.   -  person loganfsmyth    schedule 13.09.2017
comment
@loganfsmyth {} - это просто самый общий обычный тип объекта без какой-либо структуры. Конечно, это довольно бесполезно. Вопрос в том, является ли поведение, которое я наблюдал в этом пограничном случае, частью более глубокой проблемы, характерной для подтипирования.   -  person    schedule 14.09.2017
comment
Что я пытаюсь прояснить, так это то, что вы считаете пограничным случаем, и что в этом может быть проблемой? В случае с JavaScript как нетипизированным языком, есть абсолютно случаи, когда у вас будет объект, и вы ничего не знаете о его свойствах. Чтобы получить foo, вы можете сделать if(typeof o.foo === "boolean") { /* do stuff with the property as a boolean */ }, и все будет отлично.   -  person loganfsmyth    schedule 14.09.2017
comment
Возможно, было бы полезно уточнить, что {} — это объект с неизвестными свойствами, которые могут быть полезны, тогда как {||} — это объект без свойств, что довольно бесполезно.   -  person loganfsmyth    schedule 14.09.2017
comment
@loganfsmyth Я не знаю, как лучше выразить свой вопрос, извините. На самом деле, не имеет значения, рассматриваем ли мы {} как объект без или с неизвестными реквизитами. Вот еще пример: type O = {foo: boolean}; const o:O = {foo: true, bar: 123};. Это работает, потому что тип o является структурным подтипом O. Теперь, когда я пытаюсь получить доступ к o.bar, свойству, специфичному для подтипа, я получаю ошибку типа. Это не кажется правильным.   -  person    schedule 14.09.2017
comment
o — это объект со свойствами foo и bar, но поскольку вы привели его к супертипу O, система типов больше не знает, что у него есть свойство bar. Это тот же тип сбоя, который вы видите в чем-то вроде ideone.com/i9SjfW. Как только вы сказали системе типов, что это O, это все, что она знает. Да, у объекта есть и другие свойства, но вы явно сказали системе типов забыть об их наличии. Структурные типы Flow — это то, что позволяет вам приводить их, но вы столкнетесь с той же проблемой и с иерархией классов, если приведете к супертипу.   -  person loganfsmyth    schedule 14.09.2017
comment
Если вы распространите приведенные игрушечные примеры на любой реальный код, быстро станет непрактичным просить средство проверки типов проанализировать все возможные источники значения и игнорировать аннотации типов, которые вы фактически предоставили, в пользу дополнительной информации, которую она вывела.   -  person Nat Mote    schedule 14.09.2017


Ответы (1)


Общая проблема, которую вы описываете, связана с переходом от подтипа к супертипу. Выполняя приведение типов, вы явно указываете компилятору отбрасывать информацию о данном объекте.

Например, даже без структурной типизации, если вы делаете

class Animal {}

class Cat extends Animal {
  foo: bool = true;
}

const c: Animal = new Cat();

console.log(c.foo);

(On flow.org/try)

он не может проверить тип по той же причине. В вашем примере вы явно сказали компилятору «считать, что o имеет тип {}», точно так же, как в моем примере я сказал «считать, что c имеет тип Animal». Из-за этого компилятору явно было сказано забыть, что он работает с Cat, поэтому он забывает, что объект имеет свойство .foo.

Кажется, что алгоритм структурного подтипа потока иногда забывает свойства, характерные для определенного подтипа. Это поведение предназначено, или я просто столкнулся с крайним случаем?

Итак, чтобы ответить на этот вопрос, он не делает это «иногда», он делает это именно тогда, когда вы говорите ему сделать это. Поведение абсолютно преднамеренное.

Это странно, потому что AFAIK структурный подтип обычно может содержать определенные свойства, если он также включает все необходимые свойства своего супертипа.

object действительно содержит это свойство, что на 100% нормально, но вы явно стерли эту информацию, приведя значение к супертипу.

person loganfsmyth    schedule 14.09.2017
comment
Это был определенно не один из моих лучших вопросов. Спасибо, что нашли время ответить на него в любом случае! Таким образом, реальный вопрос заключается в том, как ведет себя поток, когда нет никакой явной аннотации, но тип должен быть выведен, потому что объект возвращается функцией в более сложной композиции. Потеря информации тоже есть? Связано: правило включения - person ; 14.09.2017
comment
Я чувствую, что мне нужен явный пример, но, насколько я знаю, не должно быть никакой информации, потерянной при выводе типа. - person loganfsmyth; 14.09.2017