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} 调用——它可以忽略额外的字段。