如何使用 Raku 类型系统强制执行不变性?
How to enforce immutability with the Raku typesystem?
我很欣赏不可变数据结构的价值,并且非常喜欢 Raku 内置的许多功能。我特别喜欢 compiler/typechecker 会为我强制执行不可变性——我可能会休息一天或者粗心大意一些东西,但编译器永远不会。
或者至少,我是这么想的。
然而,我很惊讶地看到下面的代码运行时没有类型检查器的窥视:
my Map $m = Hash.new('key', 'value');
say $m.WHAT # OUTPUT: «(Hash)»
查阅文档后,我看到 Map
是 Hash
的父 class(因此 Hash.isa('Map')
returns True
。所以我理解 how(在机械层面上)类型检查成功。但我有两个问题:首先 why 继承像这样工作,其次,如果我真的希望类型检查器保证我的不可变变量保持这种状态,我该怎么办。
关于“为什么”的问题 — 这样构建的地图有何不同? Raku 的其他不可变类型 None 是:Set.isa('SetHash')
、Mix.isa('MixHash')
、Bag.isa('BagHash')
、Blob.isa('Buf')
和(如果算的话)List.isa('Array')
所有 returnFalse
。 [编辑:正如 jjmerelo 在下面指出的那样,我将所有这些都颠倒了。我应该说 SetHash.isa('Set')
、MixHash.isa('Mix')
、BagHash.isa('Bag')
和 Buf.isa('Blob')
都是 return False
。有趣的是,Array.isa('List')
returns True
,这为 Elizabeth Mattijsen 的说法提供了一些支持,即这是一个历史疏忽——List
s 和 Map
s 肯定更多比大多数其他不可变类型更基本的数据类型。]
Map
和 Hash
有这种行为有何不同?
关于更实际的问题,我可以做些什么来让类型检查器在这里帮助我更多吗?我知道,在这种特定情况下,我可以写类似
my Map $m where { .WHAT === Map } = Hash.new('key', 0); # Throws the error I wanted
甚至
subset MapForRealThisTime of Map where { .WHAT === Map }
那些真的是最好的选择吗?他们都感觉有点笨拙(where
块可能有运行时成本?)但也许这是最好的方法?
更一般地说,我真正想要的是一种在严格模式下进行类型检查的方法,可以这么说。如果我显式声明变量的类型,我真的希望编译器能够保证该变量具有该确切类型——而不是恰好具有该类型作为父类型的其他类型。有没有我可以采取的更通用的方法,或者我只是要求 Raku 不会提供的严格程度?
What's different about Maps and Hashes that they have this behavior?
就我个人而言,我认为这是一个历史性的疏忽,需要在未来的某个时候修复。
Are those really the best alternatives?
我认为您正在寻找这种情况下的 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 I just asking for a level of strictness that Raku isn't going to provide
恐怕你是。您可以在 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)})
警告 这可能是 nanswer OP 的初衷。无论如何,在到达最终 nanswer 之前,我会尝试回答所有提出的问题。我猜这使它成为 部分 nanswer。
好的,让我们尝试按顺序回答所有问题。
first why does the inheritance work like that and
好吧,您要将一个值放入类型与该值兼容的容器中。这里没什么大不了的。这是 class hierarchy
A Hash
is-a
Map
,所以在那里分配它没有问题,对吧?您可以声明它 Cool
并且它无论如何都不会抱怨。如果您只是使用 my $m
,它也不会抱怨。但无论如何,容器 类型将是它声明的内容,而其内容仍将是 Hash
;如果您使用 say $m.^name
,它仍然会 return Hash
。
我们来看第二个问题:
what can I do about it if I really want the typechecker to guarantee that my immutable variables stay that way
使用绑定,而不是赋值。在这种情况下,您只能在类型完全相同或存在简单强制转换的情况下进行绑定。在这种情况下,您需要绑定到 map
my $m := Map.new('key', 'value');
通过将 Hash 分配给 Map,您并不是真的将其强制转换为 Map;您只是为仍然是 Map 的变量使用兼容的容器。即使绑定:
my Map $m := Hash.new('key', 'value');
还是不强制,还是一个Hash。您需要明确强制(我敢说 map
吗?)
my Map $m := Hash.new('key', 'value').Map;
然后,好吧,它将是不可变的。让我们进入下一个:
Set.isa('SetHash'), Mix.isa('MixHash'), Bag.isa('BagHash'), Blob.isa('Buf'), and (if it counts) List.isa('Array') all return False
嗯,Map.isa("Hash")
也 returns False
。这里的模式相同。假设 Hash
被称为 HashMap
。一样。继承只能朝着一个方向发展。您仍然可以将 SetHash 分配或绑定到 Set 变量,您仍然需要将其设置为 SetHash 以使其不可变。
is there anything I can do to get the typechecker to help me out more here?
只需分配给 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;
More generally, what I'd really like is a way to typecheck in strict mode, so to speak. If I explicitly declare the type of a variable,
好的,我明白你的意思了。 Scala 确实有办法做到这一点,您可以声明值在层次结构中的上下位置;我想这包括完全严格并且只允许类型本身。我不知道这是否是一个错误,而是一个功能。无论如何,我想不出比你提到的更好的解决方案,除非你可能需要为你想要严格类型的每个变量执行此操作,检查,因为它不能被参数化(我认为)。
我很欣赏不可变数据结构的价值,并且非常喜欢 Raku 内置的许多功能。我特别喜欢 compiler/typechecker 会为我强制执行不可变性——我可能会休息一天或者粗心大意一些东西,但编译器永远不会。
或者至少,我是这么想的。
然而,我很惊讶地看到下面的代码运行时没有类型检查器的窥视:
my Map $m = Hash.new('key', 'value');
say $m.WHAT # OUTPUT: «(Hash)»
查阅文档后,我看到 Map
是 Hash
的父 class(因此 Hash.isa('Map')
returns True
。所以我理解 how(在机械层面上)类型检查成功。但我有两个问题:首先 why 继承像这样工作,其次,如果我真的希望类型检查器保证我的不可变变量保持这种状态,我该怎么办。
关于“为什么”的问题 — 这样构建的地图有何不同? Raku 的其他不可变类型 None 是:Set.isa('SetHash')
、Mix.isa('MixHash')
、Bag.isa('BagHash')
、Blob.isa('Buf')
和(如果算的话)List.isa('Array')
所有 returnFalse
。 [编辑:正如 jjmerelo 在下面指出的那样,我将所有这些都颠倒了。我应该说 SetHash.isa('Set')
、MixHash.isa('Mix')
、BagHash.isa('Bag')
和 Buf.isa('Blob')
都是 return False
。有趣的是,Array.isa('List')
returns True
,这为 Elizabeth Mattijsen 的说法提供了一些支持,即这是一个历史疏忽——List
s 和 Map
s 肯定更多比大多数其他不可变类型更基本的数据类型。]
Map
和 Hash
有这种行为有何不同?
关于更实际的问题,我可以做些什么来让类型检查器在这里帮助我更多吗?我知道,在这种特定情况下,我可以写类似
my Map $m where { .WHAT === Map } = Hash.new('key', 0); # Throws the error I wanted
甚至
subset MapForRealThisTime of Map where { .WHAT === Map }
那些真的是最好的选择吗?他们都感觉有点笨拙(where
块可能有运行时成本?)但也许这是最好的方法?
更一般地说,我真正想要的是一种在严格模式下进行类型检查的方法,可以这么说。如果我显式声明变量的类型,我真的希望编译器能够保证该变量具有该确切类型——而不是恰好具有该类型作为父类型的其他类型。有没有我可以采取的更通用的方法,或者我只是要求 Raku 不会提供的严格程度?
What's different about Maps and Hashes that they have this behavior?
就我个人而言,我认为这是一个历史性的疏忽,需要在未来的某个时候修复。
Are those really the best alternatives?
我认为您正在寻找这种情况下的 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 I just asking for a level of strictness that Raku isn't going to provide
恐怕你是。您可以在 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)})
警告 这可能是 nanswer OP 的初衷。无论如何,在到达最终 nanswer 之前,我会尝试回答所有提出的问题。我猜这使它成为 部分 nanswer。
好的,让我们尝试按顺序回答所有问题。
first why does the inheritance work like that and
好吧,您要将一个值放入类型与该值兼容的容器中。这里没什么大不了的。这是 class hierarchy
A Hash
is-a
Map
,所以在那里分配它没有问题,对吧?您可以声明它 Cool
并且它无论如何都不会抱怨。如果您只是使用 my $m
,它也不会抱怨。但无论如何,容器 类型将是它声明的内容,而其内容仍将是 Hash
;如果您使用 say $m.^name
,它仍然会 return Hash
。
我们来看第二个问题:
what can I do about it if I really want the typechecker to guarantee that my immutable variables stay that way
使用绑定,而不是赋值。在这种情况下,您只能在类型完全相同或存在简单强制转换的情况下进行绑定。在这种情况下,您需要绑定到 map
my $m := Map.new('key', 'value');
通过将 Hash 分配给 Map,您并不是真的将其强制转换为 Map;您只是为仍然是 Map 的变量使用兼容的容器。即使绑定:
my Map $m := Hash.new('key', 'value');
还是不强制,还是一个Hash。您需要明确强制(我敢说 map
吗?)
my Map $m := Hash.new('key', 'value').Map;
然后,好吧,它将是不可变的。让我们进入下一个:
Set.isa('SetHash'), Mix.isa('MixHash'), Bag.isa('BagHash'), Blob.isa('Buf'), and (if it counts) List.isa('Array') all return False
嗯,Map.isa("Hash")
也 returns False
。这里的模式相同。假设 Hash
被称为 HashMap
。一样。继承只能朝着一个方向发展。您仍然可以将 SetHash 分配或绑定到 Set 变量,您仍然需要将其设置为 SetHash 以使其不可变。
is there anything I can do to get the typechecker to help me out more here?
只需分配给 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;
More generally, what I'd really like is a way to typecheck in strict mode, so to speak. If I explicitly declare the type of a variable,
好的,我明白你的意思了。 Scala 确实有办法做到这一点,您可以声明值在层次结构中的上下位置;我想这包括完全严格并且只允许类型本身。我不知道这是否是一个错误,而是一个功能。无论如何,我想不出比你提到的更好的解决方案,除非你可能需要为你想要严格类型的每个变量执行此操作,检查,因为它不能被参数化(我认为)。