web-dev-qa-db-fra.com

Qu'est-ce qui est un constructeur intérieur?

TL; DR:

  1. quelle est la définition précise des constructeurs internes? Dans Julia-v0.6 +, est-il juste de dire que "tout constructeur qui peut être appelé avec la signature typename{...}(...) (notez la partie {}) Est un constructeur interne"?
  2. Comme indiqué dans le commentaire ci-dessous, le constructeur externe uniquement est-il réellement un explicit inner constructor?
  3. Est-il juste d'utiliser methods pour vérifier si une méthode est un constructeur interne/externe?
  4. Quelle est la différence entre les constructeurs par défaut définis automatiquement par Julia et ceux correspondants explicitement définis par les utilisateurs?

BTW, je sais comment utiliser et quand utiliser un constructeur interne. Je savais ce qu'est un constructeur intérieur jusqu'à ce que les constructeurs extérieurs uniquement entrent et brouillent les eaux. :(

Rappelons quelques déclarations du doc :

1. Méthodes du constructeur externe

Un constructeur est comme toute autre fonction dans Julia en ce que son comportement global est défini par le comportement combiné de ses méthodes.

2. Méthodes du constructeur interne

Une méthode de constructeur interne ressemble beaucoup à une méthode de constructeur externe, avec deux différences: 1. Elle est déclarée à l'intérieur du bloc d'une déclaration de type, plutôt qu'à l'extérieur comme des méthodes normales. 2. Il a accès à une fonction spéciale existant localement appelée new qui crée des objets du type du bloc.

3. Constructeurs paramétriques

Sans constructeur interne explicitement fourni, la déclaration du type composite Point{T<:Real} Fournit automatiquement un constructeur interne, Point{T}, Pour chaque type possible T<:Real, Qui se comporte exactement comme non paramétrique les constructeurs internes par défaut le font. Il fournit également un seul constructeur Point externe général qui prend des paires d'arguments réels, qui doivent être du même type.

J'ai trouvé que inner constructor methods Ne peut pas être directement observé par methods, même methods(Foo{Int}) fonctionne, ce n'est en fait pas "comme n'importe quelle autre fonction", les fonctions génériques courantes ne peuvent pas être methodsed de cette manière.

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」

Cependant, le constructeurs externes uniquement ajoute une autre ride à l'histoire du constructeur:

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, un outer constructor IN dans un bloc de déclaration de type, et il appelle également new. Je suppose que le but ici est juste d'empêcher Julia de définir la paire de constructeurs intérieur-extérieur par défaut pour nous, mais la deuxième déclaration de la documentation est-elle toujours vraie dans ce cas? C'est déroutant pour les nouveaux utilisateurs.

Ici , j'ai lu une autre forme de constructeurs internes:

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

C'est loin de la forme canonique Foo{T}(x::T) where {T} = new(x) mais il semble que les résultats soient tout à fait les mêmes.

Donc ma question est quelle est la définition précise des constructeurs internes? Dans Julia-v0.6 +, est-il juste de dire que "tout constructeur qui peut être appelé avec la signature typename{...}(...) (notez la partie {}) Est un constructeur interne"?

32
Gnimuc

À titre d'exemple, supposons que vous souhaitiez définir un type pour représenter des nombres pairs:

Julia> struct Even
          e::Int
       end

Julia> Even(2)
Even(2)

Jusqu'ici tout va bien, mais vous voulez également que le constructeur rejette les nombres impairs et jusqu'à présent, Even(x) ne:

Julia> Even(3)
Even(3)

Vous essayez donc d'écrire votre propre constructeur comme

Julia> Even(x) = iseven(x) ? Even(x) : throw(ArgumentError("x=$x is odd"))
Even

et ... roulement de tambour, s'il vous plaît ... Cela ne fonctionne pas:

Julia> Even(3)
Even(3)

Pourquoi? Demandons à Julia ce qu'elle vient d'appeler:

Julia> @which Even(3)
Even(e::Int64) in Main at REPL[1]:2

Ce n'est pas la méthode que vous avez définie (regardez le nom de l'argument et le type), c'est le constructeur implicitement fourni. Peut-être qu'on devrait redéfinir ça? Eh bien, n'essayez pas cela à la maison:

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) 

Nous venons de créer une boucle infinie: nous avons redéfini Even(e) pour s'appeler récursivement. Nous sommes maintenant confrontés à un problème de poulet et d'oeuf: nous voulons redéfinir le constructeur implicite, mais nous avons besoin d'un autre constructeur pour appeler la fonction définie. Comme nous l'avons vu, appeler Even(e) n'est pas une option viable.

La solution est de définir un constructeur interne:

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
..

Dans un constructeur interne, vous pouvez appeler le constructeur implicite d'origine à l'aide de la syntaxe new(). Cette syntaxe n'est pas disponible pour les constructeurs externes. Si vous essayez de l'utiliser, vous obtiendrez une erreur:

Julia> Even() = new(2)
Even

Julia> Even()
ERROR: UndefVarError: new not defined 
..
9