Интроспекция

В главе Reflection and introspection ( http://docs.julialang.org/en/stable/devdocs/reflection/ ) официальной документации описан ряд функций по исследованию того, с чем мы имеем дело: объектами, функциями или модулями. В своей работе мне не приходилось пользоваться большинством из них, кроме некоторых, о которых и расскажу.

method_exists и eltype

Допустим мы написали функцию, которая создает нам генератор. Генератор может по аналогии с map применять переданную ему функцию func к каждому исходному элементу переданного ему итератора iter. Дополнительно, если опциональный параметр debug равен true итератор будет выводить отладочную информацию со значением каждого элемента до применения к нему функции func и после. Таким способом мы избавляем себя от того, чтобы встраивать методы отладки внутрь функции func: отладка сможет работать с любой функцией, которая будет использована в нашем итераторе:

julia> function infoiter( func::Function, iter, debug::Bool=true)
        ( begin
           debug && info("before: $i")
           rv = func(i)
           debug && info("after: $rv")
           rv
          end for i in iter )
       end;

Вроде бы, все хорошо: теперь мы можем создавать новые итераторы:

julia> newiter = infoiter( x->x*2, [1,2,3], true);

julia> newiter|>first
INFO: before: 1
INFO: after: 2
2

Мы можем выключить отладку:

julia> newiter2 = infoiter( x->x*2, [1,2,3], false);

julia> newiter2|>first
2

Теперь, о том, что не так с функцией infoiter. Мы могли бы случайно передать ей функцию которая, рассчитана на строковые элементы итератора, но не подходит для целых чисел. Например x->"Hello"*x*"!" :

julia> newiter3 = infoiter( x->"Hello"*x*"!", [1,2,3], false);

Ошибки не произошло, итератор создан, но если мы попробуем запросить его элемент, произойдет ошибка:

julia> newiter3 |> first
ERROR: MethodError: no method matching *(::String, ::Int64)
...

Или мы можем использовать функцию (x,y)->x+y с неправильным количеством параметров:

julia> newiter4 = infoiter( (x,y)->x+y, [1,2,3], false);

Понятно, же что это работать не будет, но ошибки не происходит, пока мы не попробуем использовать итератор.

Получается, что момент, когда мы узнаем об ошибке откладывается до использования нового итератора. Но благодаря возможностям интроспекции, мы можем выявить грубые ошибки раньше - уже в момент создания итератора.

Функция method_exists(f, Tuple type) -> Bool принимает функцию и кортеж типов параметров, соответствующих количеству, порядку и типам тех аргументов, которые предполагается ей передавать. Она возвращает true , если такой вызов будет корректным и false в противном случае. Как вы догадываетесь, она использует те же механизмы диспетчеризации, что и сам вызов исследуемой функции, поэтому о возможной "дороговизне" такой интроспекции речи не идет. Итак, добавляем простейшую проверку в функцию infoiter:

julia> function infoiter( func::Function, iter, debug::Bool=true)
        elt = iter|>eltype
        method_exists( func, (elt,) ) || error("Function $func does't applicable to eltype $elt")
        ( begin
           debug && info("before: $i")
           rv = func(i)
           debug && info("after: $rv")
           rv
          end for i in iter )
       end

Здесь eltype даем нам знание о типе элементов итератора iter. Функция method_exists дает нам добро на способ, которым новоиспеченный генератор собирается воспользоваться функцией func. Защита сработает вовремя, если мы передадим функцию, принимающую неправильное количество параметров: (x,y)->x+y:

julia> newiter4 = infoiter( (x,y)->x+y, [1,2,3], false);
ERROR: Function #51 does't applicable to eltype Int64
 in infoiter(::##51#52, ::Array{Int64,1}, ::Bool) at ./REPL[46]:3

Однако, осталась еще одна проблема: функция x->"Hello"*x*"!" никак не ограничивает тип принимаемого ею параметра, хотя при ее создании нам уже известно, что она может работать только со строками. Поэтому для нее встроенная в infoiter защита не сработает:

julia> newiter4 = infoiter( x->"Hello"*x*"!", [1,2,3], false);

julia> newiter4|>first
ERROR: MethodError: no method matching *(::String, ::Int64)

Чтобы обезопасить себя от ошибок, мы должны ограничить тип параметра строками x::AbstractString->"Hello"*x*"!":

julia> newiter5 = infoiter( x::AbstractString->"Hello"*x*"!", [1,2,3], false);
ERROR: Function #45 does't applicable to eltype Int64
 in infoiter(::##45#46, ::Array{Int64,1}, ::Bool) at ./REPL[46]:3

Теперь ошибка случилась до того, как мы начали использовать итератор.

results matching ""

    No results matching ""