Как обеспечить неизменность с помощью системы типов Raku? - PullRequest
6 голосов
/ 07 августа 2020

Я ценю неизменные структуры данных, и мне очень нравится, что в Raku много встроенных. Мне особенно нравится, что компилятор / проверка типов обеспечит неизменность для меня - у меня может быть выходной день или я буду небрежно относиться к чему-то, но компилятор никогда не сделает этого.

Или, по крайней мере, так я думал.

Однако я был очень удивлен, увидев, что следующий код запускается без пика для проверки типов:

my Map $m = Hash.new('key', 'value');
say $m.WHAT # OUTPUT: «(Hash)»

Посмотрев документацию, я вижу, что Map является родительским классом для Hash (и, таким образом, Hash.isa('Map') возвращает True. Так что я понимаю , как (на механическом уровне ), которая успешно проверяется типами. Но у меня остается два вопроса: во-первых, , почему так работает наследование, и, во-вторых, что я могу с этим поделать, если я действительно хочу, чтобы проверка типов гарантировала, что мои неизменяемые переменные

На вопрос «почему» - чем отличаются Карты, которые они построили таким образом? Ни один из других неизменяемых типов Раку не является e: Set.isa('SetHash'), Mix.isa('MixHash'), Bag.isa('BagHash'), Blob.isa('Buf') и (если учитывается) List.isa('Array') все возвращают False. [ Edit : как jjmerelo указывает ниже, я все это отменил. Я должен был сказать SetHash.isa('Set'), MixHash.isa('Mix'), BagHash.isa('Bag') и Buf.isa('Blob'), все возвращают False. Интересно, что Array.isa('List') возвращает True, что в некоторой степени подтверждает заявление Элизабет Маттийсен о том, что это исторический упущение - List s и Map s определенно являются более фундаментальными типами данных, чем большинство других неизменяемых типов.]

Чем отличаются Map s и Hash es тем, что у них такое поведение?

Что касается более практического вопроса, могу ли я что-нибудь сделать, чтобы программа проверки типов помогала мне больше? Вот? Я знаю, что в этом конкретном случае c я могу написать что-то вроде

my Map $m where { .WHAT === Map } = Hash.new('key', 0); # Throws the error I wanted

или даже

subset MapForRealThisTime of Map where { .WHAT === Map }

Действительно ли это лучшие альтернативы? Они оба кажутся немного неуклюжими (а блок where потенциально может иметь затраты времени выполнения?), Но, может быть, это лучший подход?

В общем, то, что мне действительно нужно, это способ строгой проверки типов режим, так сказать. Если я явно объявляю тип переменной, я бы очень хотел, чтобы компилятор гарантировал, что переменная имеет именно этот тип, а не какой-то другой тип, который имеет этот тип в качестве родительского. Есть ли какой-нибудь более общий подход, который я могу использовать, или я просто прошу об уровне строгости, которого Раку не собирается обеспечивать?

Ответы [ 2 ]

4 голосов
/ 07 августа 2020

Чем отличаются Карты и Хэши тем, что они имеют такое поведение?

Лично я думаю, что это историческая ошибка, которую нужно исправить в какой-то момент в будущем.

Это действительно лучшие альтернативы?

Думаю, вы ищете в этом контексте черту is:

my %m is Map = a => 42, b => 666;
dd %m;  # Map.new((:a(42),:b(666)))
%m<a> = 666; # Cannot change key 'a' in an immutable Map
%m<c> = 666; # Cannot add key 'c' to an immutable Map

am Я просто прошу уровня строгости, которого Раку не собирается обеспечивать

Боюсь, что да. Вы можете использовать оператор =:= в предложении where:

subset RealMap of Map where .WHAT =:= Map;
my RealMap $m = Map.new((a => 42)); # works
my RealMap $h = Hash.new((a => 42));
# Type check failed in assignment to $m; expected RealMap but got Hash ({:a(42)})
3 голосов
/ 07 августа 2020

Предупреждение Вероятно, это nanswer для первоначального намерения OP. В любом случае, я постараюсь ответить на все вопросы, которые будут заданы, прежде чем перейти к финальному nanswer . Полагаю, это делает его частичным безответным.

Хорошо, давайте попробуем ответить на все вопросы по порядку.

сначала почему наследование так работает и

Ну, вы ставите значение в контейнере, тип которого совместим с этим значением. Здесь нет ничего страшного. Это иерархия классов

Иерархия классов

A Hash is-a Map, так что нет проблем с его назначением, верно? Вы могли бы объявить это Cool, и он все равно не будет жаловаться. Он также не будет жаловаться, если вы просто воспользуетесь my $m. Но в любом случае тип контейнера будет тем, что он объявлен, а его содержимое все равно будет Hash; если вы используете say $m.^name, он все равно вернет Hash. Давайте go для второго вопроса:

что я могу с этим поделать, если я действительно хочу, чтобы проверка типов гарантировала, что мои неизменяемые переменные останутся такими

Использовать привязку , а не переуступка. В этом случае вы сможете выполнить привязку только в том случае, если тип точно такой же или существует простое приведение. В этом случае вам нужно привязать к map

my $m := Map.new('key', 'value');

Назначая Ha sh на карту, вы на самом деле не заставляете его создавать карту; вы просто используете совместимый контейнер для переменной, которая все еще является картой. Даже если вы привязываете:

my Map $m := Hash.new('key', 'value');

по-прежнему без принуждения, все равно Ha sh. Вам нужно явно принудить (осмелюсь сказать, map?)

my Map $m := Hash.new('key', 'value').Map;

, и тогда, ну, это будет неизменным. Давайте go к следующему:

Set.isa ('SetHa sh'), Mix.isa ('MixHa sh'), Bag.isa ('BagHa sh '), Blob.isa (' Buf ') и (если учитывается) List.isa (' Array ') все возвращают False

Ну, Map.isa("Hash") также возвращает False. Здесь тот же образец. Представьте, что Hash называется HashMap. То же самое. Наследование идет только в одном направлении. Вы по-прежнему можете назначить или привязать SetHa sh к переменной Set, вам все равно нужно будет сделать его SetHa sh, чтобы сделать его неизменным.

могу ли я что-нибудь сделать, чтобы получить

Просто назначьте Map и конвертируйте все, что присвоено Map, или объявите с нуля. Это не приведет к ошибке:

my Map $m where { .WHAT === Map } = Hash.new('key', 0).Map; 
# Map.new((key => 0))

Но вы можете просто сказать

my Map $m = Hash.new('key', 0).Map;

В общем, то, что мне действительно нужно, - это способ проверки типов в строгом режиме, поэтому говорить. Если я явно объявлю тип переменной,

ОК, теперь я понимаю, что вы имеете в виду. У Scala есть способ сделать это, вы можете объявить, где значения будут go вверх и вниз по иерархии; Я предполагаю, что это включает в себя полную строгость и разрешение только самого типа. Я не знаю, ошибка ли это, а скорее особенность. И в любом случае я не могу придумать лучшего решения, чем упомянутое вами, за исключением того, что вам, вероятно, нужно будет сделать это для каждой отдельной переменной, которую вы хотите строго ввести, проверить, поскольку она не может быть параметризована (я думаю).

...