Один из способов сделать это (если ваше тело функции чистое) - это сгенерированная функция .Например, предположим, что рассматриваемая функция была
f(x) = x + (rand(Bool) ? 1.0 : 1)
Вместо этого мы можем написать
_f(x) = x + (rand(Bool) ? 1.0 : 1)
@generated function f(x)
out_type = Core.Compiler.return_type(_f, Tuple{x})
if !isconcretetype(out_type)
error("$f($x) does not infer to a concrete type")
end
:(_f(x))
end
, теперь мы можем проверить это в repl.Входные данные с плавающей запятой хороши, но целые ошибки:
julia> f(1.0)
2.0
julia> f(1)
ERROR: f(Int64) does not infer to a concrete type
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] #s28#4(::Any, ::Any) at ./REPL[5]:4
[3] (::Core.GeneratedFunctionStub)(::Any, ::Vararg{Any,N} where N) at ./boot.jl:524
[4] top-level scope at REPL[8]:1
и из-за того, как мы использовали сгенерированные функции, проверка типов и сброс ошибок происходит только во время компиляции, поэтому мы не платим за это время выполнения.
Если вышеприведенное выглядит для вас слишком много кода котельной пластины, мы можем написать макрос для автоматической генерации внутренней функции и сгенерированной функции для произвольных сигнатур функций:
using MacroTools: splitdef, combinedef
strip_type_asserts(ex::Expr) = ex.head == :(::) ? ex.args[1] : ex
strip_type_asserts(s) = s
macro checked(fdef)
d = splitdef(fdef)
f = d[:name]
args = d[:args]
whereparams = d[:whereparams]
d[:name] = gensym()
shadow_fdef = combinedef(d)
args_stripped = strip_type_asserts.(args)
quote
$shadow_fdef
@generated function $f($(args...)) where {$(whereparams...)}
d = $d
T = Tuple{$(args_stripped...)}
shadowf = $(d[:name])
out_type = Core.Compiler.return_type(shadowf, T)
sig = collect(T.parameters)
if !isconcretetype(out_type)
f = $f
sig = reduce(*, (", $U" for U in T.parameters[2:end]), init="$(T.parameters[1])")
error("$f($(sig...)) does not infer to a concrete type")
end
args = $args
#Core.println("statically inferred return type was $out_type")
:($(shadowf)($(args...)))
end
end |> esc
end
Теперьв ответе мы просто должны аннотировать определение функции с помощью @checked
:
julia> @checked g(x, y) = x + (rand(Bool) ? 1.0 : 1)*y
f (generic function with 2 methods)
julia> g(1, 2.0)
3.0
julia> g(1, 2)
ERROR: g(Int64, Int64) does not infer to a concrete type
Stacktrace:
[1] error(::String) at ./error.jl:33
[2] #s28#5(::Any, ::Any, ::Any) at ./REPL[11]:22
[3] (::Core.GeneratedFunctionStub)(::Any, ::Vararg{Any,N} where N) at ./boot.jl:524
[4] top-level scope at REPL[14]:1
Редактировать: В комментариях было указано, что я нарушаю одно из«правила» использования сгенерированных функций здесь, потому что то, что происходит во время компиляции в сгенерированной функции, может быть автоматически отменено, если кто-то переопределит функцию, на которую опирается функция @checked
.Например:
julia> g(x) = x + 1;
julia> @checked f(x) = g(x) + 1;
julia> f(1) # shouldn't error
3
julia> g(x) = rand(Bool) ? 1.0 : 1
g (generic function with 1 method)
julia> f(1) # Should error but doesn't!!!
2.0
julia> f(1)
2
Так что будьте осторожны: если вы используете что-то подобное в интерактивном режиме, будьте осторожны с переопределением функций, на которые вы полагаетесь.Если по какой-либо причине вы решите использовать этот макрос в пакете, имейте в виду, что люди, совершающие пиратство типов, лишат вас возможности проверять тип.
Если кто-то попытается применить эту технику к важному коду, я бы предложиллибо пересмотреть, либо серьезно подумать, как сделать это безопаснее.Если у вас есть идеи по поводу того, как сделать его более безопасным, я бы хотел их услышать!Возможно, есть некоторые приемы, которые вы можете использовать для принудительной перекомпиляции функции каждый раз, когда изменяется зависимый метод.