Какой внутренний конструктор?
TL; DR:
- какое точное определение внутренних конструкторов? В Julia-v0.6 +, правильно ли сказать "любой конструктор, который можно вызывать с сигнатурой
typename{...}(...)
(обратите внимание на часть {}
), является внутренним конструктором"?
- Как обсуждалось в комментарии ниже, конструктор только для внешнего интерфейса фактически
explicit inner constructor
?
- Можно ли использовать
methods
для проверки того, является ли метод внутренним/внешним конструктором?
- Какая разница между конструкторами по умолчанию, которые автоматически определяются Julia и соответствующими, явно определенными пользователями?
Кстати, я знаю, как использовать и когда использовать внутренний конструктор. Я знал, что такое внутренний конструктор, до тех пор, пока внешние конструкторы не войдут и не замутнут воды.:(
Вспомним некоторые утверждения из doc:
1. Методы внешнего конструктора
Конструктор аналогичен любой другой функции в Julia, поскольку его общее поведение определяется комбинированным поведением его методов.
2. Внутренние конструкторские методы
Метод внутреннего конструктора очень похож на метод внешнего конструктора с двумя отличиями: 1. Он объявляется внутри блока объявления типа, а не вне его, как обычные методы. 2. Он имеет доступ к специальной локально существующей функции под названием new
, которая создает объекты типа блока.
3. Параметрические конструкторы
Без каких-либо явно предоставляемых внутренних конструкторов объявление составного типа Point{T<:Real}
автоматически предоставляет внутренний конструктор Point{T}
для каждого возможного типа T<:Real
, который ведет себя так же, как и непараметрические внутренние конструкторы по умолчанию. Он также предоставляет один общий внешний конструктор Point, который принимает пары реальных аргументов, которые должны быть одного типа.
Я обнаружил, что inner constructor methods
не может быть непосредственно наблюдаемым methods
, даже methods(Foo{Int})
работает, он фактически не "точно так же, как любая другая функция", общие общие функции не могут быть methods
ed таким образом.
julia> struct Foo{T}
x::T
end
julia> methods(Foo)
# 2 methods for generic function "(::Type)":
(::Type{Foo})(x::T) where T in Main at REPL[1]:2 # outer ctor 「1」
(::Type{T})(arg) where T in Base at sysimg.jl:24 # default convertion method「2」
julia> @which Foo{Int}(1) # or methods(Foo{Int})
(::Type{Foo{T}})(x) where T in Main at REPL[1]:2 # inner ctor 「3」
Тем не менее, внешние конструкторы добавляет еще одну морщину в историю конструктора:
julia> struct SummedArray{T<:Number,S<:Number}
data::Vector{T}
sum::S
function SummedArray(a::Vector{T}) where T
S = widen(T)
new{T,S}(a, sum(S, a))
end
end
julia> methods(SummedArray)
# 2 methods for generic function "(::Type)":
(::Type{SummedArray})(a::Array{T,1}) where T in Main at REPL[1]:5 # outer ctor「4」
(::Type{T})(arg) where T in Base at sysimg.jl:24
Hmmm, a outer constructor
В блоке объявления типа, и он также вызывает new
. Я предполагаю, что цель здесь состоит в том, чтобы не допустить, чтобы Джулия определяла для меня внутреннюю конструкторскую пару по умолчанию, но является ли второе утверждение из документации по-прежнему истинным в этом случае? Это запутывает новых пользователей.
Здесь, я прочитал другую форму внутренних конструкторов:
julia> struct Foo{T}
x::T
(::Type{Foo{T}})(x::T) = new{T}(x)
end
julia> methods(Foo)
# 1 method for generic function "(::Type)":
(::Type{T})(arg) where T in Base at sysimg.jl:24
julia> methods(Foo{Int})
# 2 methods for generic function "(::Type)":
(::Type{Foo{T}})(x::T) where T in Main at REPL[2]:3 「5」
(::Type{T})(arg) where T in Base at sysimg.jl:24
Это далеко от канонической формы Foo{T}(x::T) where {T} = new(x)
, но, похоже, результаты совершенно одинаковы.
Итак, мой вопрос - точное определение внутренних конструкторов? В Julia-v0.6 +, правильно ли сказать "любой конструктор, который можно вызывать с сигнатурой typename{...}(...)
(обратите внимание на часть {}
), является внутренним конструктором"?
Ответы
Ответ 1
В качестве примера предположим, что вы хотите определить тип для представления четных чисел:
julia> struct Even
e::Int
end
julia> Even(2)
Even(2)
До сих пор так хорошо, но вы также хотите, чтобы конструктор отклонил нечетные числа, и пока Even(x)
не делает:
julia> Even(3)
Even(3)
Итак, вы пытаетесь написать свой собственный конструктор как
julia> Even(x) = iseven(x) ? Even(x) : throw(ArgumentError("x=$x is odd"))
Even
и... барабан, пожалуйста... Это не работает:
julia> Even(3)
Even(3)
Почему? Позвольте спросить у Джулии, что она только что назвала:
julia> @which Even(3)
Even(e::Int64) in Main at REPL[1]:2
Это не тот метод, который вы определили (посмотрите имя аргумента и тип), это неявно предоставленный конструктор. Может быть, нам следует переопределить это? Ну, не пробуйте это дома:
julia> Even(e::Int) = iseven(e) ? Even(e) : throw(ArgumentError("e=$e is odd"))
Even
julia> Even(2)
ERROR: StackOverflowError:
Stacktrace:
[1] Even(::Int64) at ./REPL[11]:0
[2] Even(::Int64) at ./REPL[11]:1 (repeats 65497 times)
Мы только что создали бесконечный цикл: мы переопределили Even(e)
, чтобы рекурсивно называть себя. Теперь мы сталкиваемся с проблемой курицы и яйца: мы хотим переопределить неявный конструктор, но нам нужен другой конструктор для вызова определенной функции. Как мы видели, вызов Even(e)
не является жизнеспособным вариантом.
Решение состоит в том, чтобы определить внутренний конструктор:
julia> workspace()
julia> struct Even
e::Int
Even(e::Int) = iseven(e) ? new(e) : throw(ArgumentError("e=$e is odd"))
end
julia> Even(2)
Even(2)
julia> Even(3)
ERROR: ArgumentError: e=3 is odd
..
Внутри внутреннего конструктора вы можете вызвать исходный неявный конструктор, используя синтаксис new()
. Этот синтаксис недоступен внешним конструкторам. Если вы попытаетесь использовать его, вы получите сообщение об ошибке:
julia> Even() = new(2)
Even
julia> Even()
ERROR: UndefVarError: new not defined
..