指定 Julia 函数只能采用其内容为特定类型的字典/数组的正确方法是啥?

Posted

技术标签:

【中文标题】指定 Julia 函数只能采用其内容为特定类型的字典/数组的正确方法是啥?【英文标题】:What is the correct way to specify that a Julia function can only take Dicts/Arrays whose contents are of certain types?指定 Julia 函数只能采用其内容为特定类型的字典/数组的正确方法是什么? 【发布时间】:2020-08-11 15:20:15 【问题描述】:

假设我有一个函数需要一个 dict 作为输入。在此范围内,此函数只能处理允许类型的某个Union 中的该字典的值。对于这个参数,输入可以是NumberStringBool

allowed_types = UnionString, Int, AbstractFloat, Bool

该函数还可以允许其值为这些允许类型 (DictString,allowed_types) 的 Dict 或其项为这些类型 (Arrayallowed_types,Int) 的数组,因为它们可以“分解”为其中一种允许的类型。 (这可以继续向下 - 所以数组数组等)

full_allowed_types = Unionallowed_types, DictString,allowed_types, Arrayallowed_types,Int

然后我可以将我的函数定义为

function my_func(input::DictString,full_allowed_types)
...
end

那么,我如何构造函数参数以便可以传递,即my_func(Dict("a"=>"astr","b"=>1))?通常,Dict(...) 调用会导致DictString,Any,因为Any 不是允许的类型,所以不能使用我的函数调用。

我当前的实现遇到的错误是:

my_func(Dict("a"=>"astr","b"=>1))
ERROR: MethodError: no method matching my_func(::DictString,Any)
Closest candidates are:
  my_func(::DictString,UnionBool, Int64, DictString,UnionBool, Int64, AbstractFloat, String, AbstractFloat, ArrayUnionBool, Int64, AbstractFloat, String,Int64, String) at <snip>/my_func.jl:41
Stacktrace:
 [1] top-level scope at none:0

我从用户的角度来描绘这个问题,用户可能只是使用默认构造函数创建一个 dict,而不考虑 my_func 想要“允许”什么(这意味着我不希望他们调用DictString,my_pkg.full_allowed_types(...))。

最好的选择是只允许Any 作为my_func 的输入,然后在我遍历输入时如果任何元素不符合我允许的类型则抛出错误?

【问题讨论】:

小心Arrayallowed_types,Int,这不是你想要的。 Array 的第二个泛型参数不是一个类型,而是一个实际的整数。例如。整数向量的类型为VectorInt, 1。因此,您可能只想使用与Arrayfull_allowed_types,N where N 相同的Arrayfull_allowed_types 没有测试,我很确定你可以表达这些类型约束,是的。但是你确定你需要吗? Julia 的良好做法是尽可能地允许类型并编写通用代码。为什么必须将类型限制为字符串、整数和浮点数?也许你有充分的理由,但在你创建一个超级复杂的类型签名之前要确定,这可能是错误的和不必要的限制。 @DNF 限制的主要原因是我正在编写一个 TOML 打印机,而 TOML 规范只允许某些类型的变量。不过,由于我一直在玩这个,我可能会更坚持允许用户输入任何内容,并尝试将这些输入强制转换为 TOML 允许的内容,如果我不能,则抛出错误。 【参考方案1】:
function f(a::DictString, A) where A <: UnionInt,String
   println("Got elem type $A")
end

用法:

julia> f(DictString,UnionString,Int("a"=>"astr","b"=>1))
Got elem type UnionInt64, String

现在,如果您想为用户提供方便,您可以添加一个附加功能(但是,类型转换是有代价的):

function f(a::DictString,A) where A
    @warn "Provided unsupported type of elements $A will try narrow it to UnionInt,String"
    f(DictString,UnionInt,String(d))
end

示例用法:


julia> f(Dict("a"=>"astr","b"=>1))
┌ Warning: Provided unsuported type of elements Any will try narrow it to UnionInt,String
└ @ Main REPL[31]:2
Got elem type UnionInt64, String

【讨论】:

【参考方案2】:

首先,在创建字典时,您可以像这样指定参数:

d = DictString, full_allowed_types("a" => "astr", "b" => 2)

其次,您创建my_func 的方式实际上是正确的,并且适用于d。但是当你创建一个字典时

d2 = DictString, Int64("a" => 1, "b" => 2)

您可能会感到惊讶,您不能用d 调用my_func。原因是,即使Int64full_allowed_types 的子类型,您也只允许DictString, full_allowed_types 类型的字典而不是DictString, Int64。如果你也想传递子类型,你可以声明my_funclike

function my_func(input::DictString, <: full_allowed_types)
    ...
end

注意这里额外的&lt;:,这只是语法糖

function my_func(input::DictString, T) where T <: full_allowed_types
    ...
end

【讨论】:

太好了,这正是我想要的!出于好奇,是否存在类似的逻辑来捕捉额外的关卡? IE - 我正在写一个 TOML 打印机,所以my_func 的输入可能有很多级别 - IE 输入字典本身可能有一个包含数组的字典......等等等等。有没有办法说“只要这条链中的最后一个元素在我的union 中,我们就可以了”? 我认为这是不可能的,即使它在哪里,那也是相当危险的。每当您在 Julia 中调用带有一些参数的函数时,Julia 都会检查是否已经存在带有该参数组合的代码。如果不是,则生成新代码。 (除非使用了`@nospecialize 宏)。因此,由于 TOML 基本上是一棵树,因此对于该树的每种不同形式,都会创建新代码。

以上是关于指定 Julia 函数只能采用其内容为特定类型的字典/数组的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

Julia:如何根据具有特定值的类型字段访问类型数组中的元素

Julia 中函数的抽象类型和多次调度

R之字符串连接函数paste

SQLite数据类型

memset用法总结

Pandas DataFrame 删除带有特定值的行