Elixir / Erlang Dialyzer:为什么行为回调的参数类型应该是子类型而不是超类型?
Elixir / Erlang Dialyzer : Why behaviour callback's param type should be subtype instead of supertype?
我有一个行为 X 和一个回调函数,参数类型为:
%{a: any}
模块Y实现行为X,实现模块Y的回调函数参数类型:
%{a: any, b: any}
Dialyzer 不喜欢并抱怨:
(#{'a':=_, 'b':=_, _=>_})
is not a supertype of
#{'a':=_}
这意味着 dialyzer 尝试确定实现模块 Y 中的回调参数类型是否是行为 X 中参数类型的超类型。换句话说,它会询问:
Is behaviour X's callback param type %{a: any}
a subtype of
implementing module Y's param type %{a: any, b: any}
?
为什么 dialyzer 期望行为回调的参数类型是子类型而不是超类型?
在编程语言类型理论的背景下,subtype is defined如:
type S is a subtype of a type T, written S <: T, if an expression of
type S can be used in any context that expects an element of type T.
Another way of putting this is that any expression of type S can
masquerade as an expression of type T.
根据上面的定义,行为回调的参数类型是T
,实现模块的参数类型是S
对我来说是有意义的。因为实现模块仍然保持行为契约。但是,我不知道透析器为什么期望相反的方式。
请帮助我理解这一点。
注意:此问题是后续问题,但独立于另一个 SO 问题 。
透析器是正确的。如果存在 X
回调类型 %{a: any}
的行为,用户应该能够调用任何声称实现此行为的模块的函数,例如%{a: 1}
。您的模块的函数采用 %{a: any, b: any}
,它是 %{a: any}
的 子类型 ,这意味着该函数不能再用 %{a: 1}
调用,这不符合行为。
另一方面,如果行为的回调具有 %{a: any, b: any}
类型并且您的模块的函数具有类型 %{a: any}
,那会很好,因为 %{a: any}
是%{a: any, b: any}
并且您的模块可以用 %{a: 1, b: 2}
调用——它可以忽略额外的字段。
我有一个行为 X 和一个回调函数,参数类型为:
%{a: any}
模块Y实现行为X,实现模块Y的回调函数参数类型:
%{a: any, b: any}
Dialyzer 不喜欢并抱怨:
(#{'a':=_, 'b':=_, _=>_})
is not a supertype of
#{'a':=_}
这意味着 dialyzer 尝试确定实现模块 Y 中的回调参数类型是否是行为 X 中参数类型的超类型。换句话说,它会询问:
Is behaviour X's callback param type
%{a: any}
a subtype of implementing module Y's param type%{a: any, b: any}
?
为什么 dialyzer 期望行为回调的参数类型是子类型而不是超类型?
在编程语言类型理论的背景下,subtype is defined如:
type S is a subtype of a type T, written S <: T, if an expression of type S can be used in any context that expects an element of type T. Another way of putting this is that any expression of type S can masquerade as an expression of type T.
根据上面的定义,行为回调的参数类型是T
,实现模块的参数类型是S
对我来说是有意义的。因为实现模块仍然保持行为契约。但是,我不知道透析器为什么期望相反的方式。
请帮助我理解这一点。
注意:此问题是后续问题,但独立于另一个 SO 问题
透析器是正确的。如果存在 X
回调类型 %{a: any}
的行为,用户应该能够调用任何声称实现此行为的模块的函数,例如%{a: 1}
。您的模块的函数采用 %{a: any, b: any}
,它是 %{a: any}
的 子类型 ,这意味着该函数不能再用 %{a: 1}
调用,这不符合行为。
另一方面,如果行为的回调具有 %{a: any, b: any}
类型并且您的模块的函数具有类型 %{a: any}
,那会很好,因为 %{a: any}
是%{a: any, b: any}
并且您的模块可以用 %{a: 1, b: 2}
调用——它可以忽略额外的字段。