36 votes

Qu'est-ce que c'est qu'un constructeur interne ?

TL;DR :

  1. quelle est la définition exacte des constructeurs internes ? Dans Julia-v0.6+, est-il correct de dire "tout constructeur qui peut être appelé avec la signature typename{...}(...) (notez le {} partie) est un constructeur interne" ?
  2. Comme discuté dans le commentaire ci-dessous, est-ce que le constructeur externe seulement est en fait une 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 qui sont définis automatiquement par Julia et les constructeurs correspondants définis explicitement par les utilisateurs ?

BTW, je sais comment utiliser et quand utiliser un constructeur interne. Je savais ce qu'était un constructeur interne jusqu'à ce que le programme Constructeurs externes uniquement est arrivé et a brouillé les pistes. :(

Rappelons quelques déclarations de la doc :

1. Méthodes du constructeur externe

Un constructeur est juste comme n'importe quelle autre fonction dans Julia dans la mesure où 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, à deux différences près : 1. Elle est déclarée à l'intérieur du bloc de la déclaration de type, plutôt qu'à l'extérieur de celui-ci comme les méthodes normales. 2. Elle 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 aucun 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 les constructeurs internes par défaut non paramétriques. Il fournit également un seul constructeur externe général Point qui prend des paires d'arguments réels, qui doivent être du même type.

J'ai trouvé inner constructor methods ne peut pas être observé directement 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 communes ne peuvent pas être methods de cette façon.

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 un autre aspect à l'histoire des constructeurs :

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 DANS un bloc de déclaration de type, et il appelle new également. Je suppose que le but ici est juste d'empêcher Julia de définir la paire de constructeurs inner-outer 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.

Ma question est donc la suivante : quelle est la définition exacte des constructeurs internes ? Dans Julia-v0.6+, est-il correct de dire "tout constructeur qui peut être appelé avec la signature typename{...}(...) (notez le {} partie) est un constructeur interne" ?

23voto

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

julia> struct Even
          e::Int
       end

julia> Even(2)
Even(2)

Jusque là tout va bien, mais vous voulez aussi que le constructeur rejette les nombres impairs et jusque là Even(x) ne le fait pas :

julia> Even(3)
Even(3)

Vous essayez donc d'écrire votre propre constructeur sous forme de

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 devrions-nous le redéfinir ? 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 au problème de l'œuf et de la poule : nous voulons redéfinir le constructeur implicite, mais nous avons besoin d'un autre constructeur à appeler dans la fonction définie. Comme nous l'avons vu, appeler Even(e) n'est pas une option viable.

La solution consiste à 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
..

À l'intérieur d'un constructeur interne, vous pouvez appeler le constructeur implicite d'origine en utilisant la balise new() syntaxe. 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 
..

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X