【问题标题】:Elixir / Erlang Dialyzer : Why behaviour callback's param type should be subtype instead of supertype?Elixir / Erlang Dialyzer:为什么行为回调的参数类型应该是子类型而不是超类型?
【发布时间】:2018-03-01 01:56:23
【问题描述】:

我有一个行为 X 和一个带有参数类型的回调函数:

%{a: any}

模块Y实现行为X,实现模块Y的回调函数有参数类型:

%{a: any, b: any}

Dialyzer 不喜欢这样并抱怨:

(#{'a':=_, 'b':=_, _=>_}) 
is not a supertype of 
#{'a':=_}

这意味着透析器尝试确定实现模块 Y 中的回调参数类型是否是行为 X 中的参数类型的超类型。换句话说,它要求:

行为 X 的回调参数类型 %{a: any} 是 实现模块Y的参数类型%{a: any, b: any}?

为什么透析器期望行为回调的参数类型是子类型而不是超类型?

在编程语言类型论的上下文中,subtype is defined 为:

类型 S 是类型 T 的子类型,写作 S <: t s>

根据上面的定义,如果行为回调的参数类型是T,而实现模块的参数类型是S,对我来说是有意义的。因为实现模块仍然保持行为契约。但是,我不知道为什么透析器期望相反。

请帮助我理解这一点。

注意:此问题是后续问题,但独立于另一个 SO 问题 Erlang (Elixir) Dialyzer - confusing supertype error

【问题讨论】:

  • Because implementing module still keeps the behaviour contract. 我不这么认为。现在你有一个模块,你说它实现了X,但实际上没有人可以用%{a: any} 调用函数,因为你的实现还需要映射中的b: any
  • 尽管实现模块 Y 需要在映射中额外添加 b: any,但最好允许对行为 X 指定的原始类型执行任何操作。它遵守宽度子类型规则:en.wikipedia.org/wiki/Subtyping#Width_and_depth_subtyping
  • "它仍然允许对行为 X 指定的原始类型的所有操作" 根据行为,我应该能够将 %{a: 1} 传递给 X 的所有实现者,但我不能这样做Y 因为它需要一个额外的字段:b。反过来也可以——如果 X 需要 %{a: any, b: any}Y 需要 %{a: any},那很好,因为我可以像行为所说的那样调用它,b 将被 Y 忽略。
  • 刚刚度过了愉快的时光!没有从那个角度思考。非常感谢。这非常有用。您能否将其放在答案中以便我接受?与阅读 cmets 相比,我们更有可能阅读已接受的答案。

标签: erlang elixir phoenix-framework dialyzer


【解决方案1】:

透析器是正确的。如果存在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} 调用——它可以忽略额外的字段。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-25
    • 1970-01-01
    • 1970-01-01
    • 2018-05-09
    • 2023-01-17
    • 2017-06-07
    • 1970-01-01
    • 2014-04-09
    相关资源
    最近更新 更多