指定 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
中的该字典的值。对于这个参数,输入可以是Number
、String
或Bool
:
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
。原因是,即使Int64
是full_allowed_types
的子类型,您也只允许DictString, full_allowed_types
类型的字典而不是DictString, Int64
。如果你也想传递子类型,你可以声明my_func
like
function my_func(input::DictString, <: full_allowed_types)
...
end
注意这里额外的<:
,这只是语法糖
function my_func(input::DictString, T) where T <: full_allowed_types
...
end
【讨论】:
太好了,这正是我想要的!出于好奇,是否存在类似的逻辑来捕捉额外的关卡? IE - 我正在写一个 TOML 打印机,所以my_func
的输入可能有很多级别 - IE 输入字典本身可能有一个包含数组的字典......等等等等。有没有办法说“只要这条链中的最后一个元素在我的union
中,我们就可以了”?
我认为这是不可能的,即使它在哪里,那也是相当危险的。每当您在 Julia 中调用带有一些参数的函数时,Julia 都会检查是否已经存在带有该参数组合的代码。如果不是,则生成新代码。 (除非使用了`@nospecialize 宏)。因此,由于 TOML 基本上是一棵树,因此对于该树的每种不同形式,都会创建新代码。以上是关于指定 Julia 函数只能采用其内容为特定类型的字典/数组的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章