Julia: autorise la surcharge de la syntaxe d'accès aux champs ab

Créé le 10 janv. 2013  ·  249Commentaires  ·  Source: JuliaLang/julia

Commentaire le plus utile

Voici une implémentation amusante en 3 lignes de ceci:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Je pense que a.b devrait en fait appeler une fonction de projection Field{:b}() au lieu de getfield , de sorte que vous obteniez déjà gratuitement des fonctions comme x->x.a . Cela permet également à getfield de toujours signifier un accès aux champs de bas niveau.

L'implémentation ci-dessus fonctionne complètement mais est assez difficile pour le compilateur (sysimg + 5%, ce qui est vraiment une agréable surprise). Cela nécessitera donc des heuristiques de spécialisation et certaines optimisations précoces doivent être mises à jour, mais nous espérons que cela sera viable.

Tous les 249 commentaires

La possibilité d'utiliser des points comme sucre syntaxique pour les méthodes de mutateur / accesseur serait bien pour beaucoup de choses. J'ai toujours apprécié cela dans les langages qui le fournissent, afin que vous puissiez transformer les champs de structure en abstractions plus compliquées sans casser l'API.

+1

J'ai un moyen absolument génial de mettre en œuvre cela.

Envie d'en parler? Je sais que Tom Short est vraiment intéressé à avoir cela pour DataFrames, bien que je sois de plus en plus sceptique quant à la sagesse d'utiliser cette fonctionnalité.

Cela rendrait l'appel du code Python (via PyCall) beaucoup plus agréable, car actuellement je suis obligé de faire a[:b] au lieu de a.b .

@JeffBezanson , une chance d'avoir ça pour 0,3? Serait idéal pour l'interopérabilité inter-langage, à la fois pour PyCall et pour JavaCall (cc @aviks).

@JeffBezanson si _non_, y a-t-il une chance que vous puissiez donner des indications sur la façon dont vous voulez que cela soit mis en œuvre? ( I have an absolutely awesome way to implement this. )

D'après mon expérience, il n'y a pas de moyen plus rapide ni plus sûr d'amener Jeff à implémenter quelque chose que d'en implémenter une version qu'il n'aime pas ;-)

L'idée de base est que vous implémentez

getfield(x::MyType, ::Field{:name}) = ...

afin que vous puissiez le surcharger par champ. Cela permet d'accéder à des champs «réels» pour continuer à travailler de manière transparente. Avec des solutions getfield(::MyType, ::Symbol) secours appropriées,

Le plus gros problème est que les modules ont un comportement spécial par rapport à . . En théorie, ce serait juste une autre méthode de getfield , mais le problème est que nous devons résoudre les références de module _earlier_ car elles se comportent fondamentalement comme des variables globales. Je pense que nous devrons garder ce cas particulier dans le comportement de . . Il y a aussi un problème d'efficacité du compilateur, en raison de l'analyse des définitions de fonctions supplémentaires (# types) * (# champs). Mais pour cela, nous verrons simplement ce qui se passe.

@JeffBezanson Faites-vous également référence au comportement de const dans les modules? Il serait utile d'avoir un type d'utilisateur émulant un module et de pouvoir dire au compilateur quand le résultat d'une recherche de champ dynamique est en fait une constante. (une autre approche serait de commencer avec un module réel et de pouvoir "piéger" un jl_get_global échec et d'injecter de nouvelles liaisons à la demande)

Je trouverais cela très utile en combinaison avec # 5395. Ensuite, on peut intercepter un appel à une fonction ou une méthode non définie MyMod.newfunction(new signature) et générer des liaisons à une (éventuellement grande) API à la demande. Ce serait alors mis en cache comme des liaisons const habituelles, je suppose.

Permettez-moi, un simple débutant de Julia, de présenter une petite inquiétude: je pense que la possibilité de surcharger l'opérateur point pourrait impliquer que la "pureté" d'accès au champ est en quelque sorte perdue.

L'utilisateur perdrait généralement la connaissance si faire ab est juste un accès à une référence / valeur ou s'il peut y avoir une énorme machine de fonction appelée derrière. Je ne sais pas comment cela pourrait être mauvais, c'est juste un sentiment ...

Par contre, je vois qu'en effet c'est un gros souhait de sucre de syntaxe dans de nombreux cas (PyCall, Dataframes ...), ce qui est parfaitement compréhensible.
Il est peut-être temps pour .. # 2614?

Je soutiens cela.

Mais la pureté a quelque chose à dire, même si l'on peut utiliser names(Foo) pour déterminer quels sont les composants réels de Foo .

L'argument de pureté est étroitement lié à la principale préoccupation pratique que j'ai, à savoir comment on gère les conflits de nom lorsque les champs du type interfèrent avec les noms que vous pourriez espérer utiliser. Dans DataFrames, je pense que nous résoudrions ce problème en interdisant l'utilisation de columns et colindex comme noms de colonne, mais nous voulions savoir quel était le plan des gens pour cela.

Je suppose que getfield(x::MyType, ::Field{:foo}) = ... devrait être interdit lorsque MyType a un champ foo , sinon l'accès au champ réel serait perdu (ou un moyen de forcer l'accès au champ devrait être disponible).
Mais alors getfield ne pourrait être défini que pour des types concrets, puisque les abstraits ne savent rien des champs.

(Pendant ce temps, je suis tombé dessus à propos de C ++ .)

Ce n'est pas un problème majeur. Nous pouvons fournir quelque chose comme Core.getfield(x, :f) pour forcer l'accès aux champs réels.

Ok, peut-être que je suis vendu. Mais alors définir un raccourci vers Core.getfield(x, :f) (par exemple, x..f ) sera bien, sinon le code interne des types surchargeant le . pour tous les symboles (dataframes, probablement dictionnaires) doit être bondé de Core.getfield s.

Je ne m'inquiète pas pour l'aspect pureté - jusqu'à ce que nous ayons cela, le seul code
qui devrait utiliser l'accès aux champs est du code qui appartient au
mise en œuvre d'un type donné. Lorsque l'accès aux champs fait partie d'une API, vous
doivent le documenter, comme avec n'importe quelle api. Je suis d'accord que cela pourrait être pratique avec
une syntaxe de raccourci pour core.getfield cependant, lors de l'écriture de ceux-ci
implémentations.

Cela avait déjà été souligné dans # 4935, mais tirons-le ici: la surcharge de points peut se chevaucher un peu avec la distribution multiple Julian classique si elle n'est pas correctement utilisée, car nous pouvons commencer à faire

getfield (x :: MyType, :: Field {: size}) = .........
pour i = 1: y.size .....

au lieu de

taille (x :: MyType) = ..........
pour i = 1: taille (y) ....

Bien que le point soit idéal pour accéder aux éléments des collections (Dataframes, Dicts, PyObjects), il peut en quelque sorte modifier la façon dont les propriétés des objets (et non les champs) sont accessibles.

Je pense qu'une chose à considérer est que si vous pouvez surcharger un champ d'accès, vous devriez également être en mesure de surcharger un champ. Sinon, ce sera incohérent et frustrant. Êtes-vous d'accord pour aller aussi loin?

@nalimilan , il faut absolument un setfield! en plus de getfield . (Similaire à setindex! vs getindex pour [] ). Je ne pense pas que ce soit controversé.

D'accord avec @stevengj : DataFrames implémentera certainement setfield! pour les colonnes.

Je soutiens cela.

L'expérience avec d'autres langages (par exemple C # et Python) montre que la syntaxe dot a beaucoup de valeur pratique. La manière dont il est mis en œuvre par des méthodes spécialisées répond en grande partie au problème de la régression des performances.

Il est cependant important de s'assurer que l '_inlineabilité_ d'une méthode ne sera pas sérieusement affectée par ce changement. Par exemple, quelque chose comme f(x) = g(x.a) + h(x.b) ne deviendra soudainement pas insignable après que cela arrive.

Si nous décidons de faire en sorte que cela se produise, il est utile de fournir également des macros pour faciliter la définition de la propriété, ce qui pourrait ressembler à:

# let A be a type, and foo a property name
<strong i="10">@property</strong> (a::A).foo = begin
    # compute the return the property value
end

# for simpler cases, this can be simplified to
<strong i="11">@property</strong> (a::A).foo2 = (2 * a.foo)

# set property 
<strong i="12">@setproperty</strong> (a::A).foo v::V begin
    # codes for setting value v to a property a.foo
end

Derrière la scène, tout cela peut être traduit dans les définitions de méthode.

Je ne suis pas convaincu que <strong i="5">@property</strong> (a::A).foo = soit beaucoup plus simple que getproperty(a::A, ::Field{foo}) = ...

Dans tous les cas, un meilleur sucre syntaxique peut être ajouté après l'arrivée des fonctionnalités de base.

En ce qui concerne l'inlining, tant que l'accès au champ est inline avant que la décision ne soit prise d'inline ou non la fonction environnante, alors je ne vois pas pourquoi cela serait impacté. Mais ce n'est peut-être pas l'ordre dans lequel l'inlining est actuellement effectué?

getproperty(a::A, ::Field{:foo}) = me frappe car il y a trop de deux-points :-) Je suis d'accord que c'est une chose mineure, et probablement nous n'avons pas besoin de nous en préoccuper pour le moment.

Ma préoccupation est de savoir si cela entraînerait une régression des performances. Je ne suis pas très clair sur le mécanisme de génération de code interne. @JeffBezanson peut probablement dire quelque chose à ce sujet?

L'accès au champ est de très bas niveau, je ne le ferai donc pas sans m'assurer que les performances sont préservées.

Après tout, je ne suis pas convaincu que surcharger les champs soit une bonne idée. Avec cette proposition, il y aurait toujours deux façons de définir une propriété: x.property = value et property!(x, value) . Si la surcharge de champ est implémentée, nous aurons besoin d'un guide de style très fort pour éviter de finir dans un désordre total où vous ne savez jamais à l'avance quelle solution l'auteur a choisie pour un type donné.

Et puis il y aurait la question de savoir si les champs sont publics ou privés. Ne pas autoriser la surcharge de champ rendrait le système de types plus clair: les champs seraient toujours privés. Les méthodes seraient publiques, et les types seraient capables de déclarer qu'ils implémentent des interfaces / protocoles / traits, c'est-à-dire qu'ils fournissent un ensemble donné de méthodes. Cela irait à l' encontre @stevengj est https://github.com/JuliaLang/julia/issues/1974#issuecomment -12.083.268 au sujet de la surcharge des champs avec des méthodes pour éviter de casser une API: offrir uniquement des méthodes dans le cadre de l'API, et jamais les champs .

Le seul endroit où je regretterais une surcharge de champ est pour DataFrames , puisque df[:a] n'est pas aussi bien que df.a . Mais cela ne semble pas devoir exiger à lui seul un changement aussi important. L'autre cas d'utilisation semble être PyCall, ce qui peut indiquer que la surcharge de champ devrait être autorisée, mais uniquement pour des cas d'utilisation non juliens très spécifiques. Mais comment empêcher les gens d'utiliser abusivement une fonctionnalité une fois qu'elle est disponible? Le cacher dans un module spécial?

@nalimilan , je dirais que la préférence devrait être d'utiliser autant que possible la syntaxe x.property . Le fait est que les gens aiment _ vraiment_ cette syntaxe - c'est très agréable. Prendre une si belle syntaxe et dire spécifiquement qu'elle ne devrait être utilisée que pour l'accès interne aux objets semble carrément pervers - "hah, cette belle syntaxe existe; ne l'utilisez pas!" Il semble beaucoup plus raisonnable de rendre la syntaxe pour accéder aux choses privées moins pratique et moins jolie au lieu de forcer les API à utiliser la syntaxe la plus moche. C'est peut-être un bon cas d'utilisation pour l'opérateur .. : l'opérateur d'accès au champ privé _real_.

Je pense en fait que ce changement peut rendre les choses plus claires et plus cohérentes plutôt que moins. Considérez les gammes - il existe actuellement une sorte de mélange hideux de styles step(r) contre r.step en ce moment. Surtout depuis que j'ai introduit FloatRange c'est dangereux car seul le code qui utilise step(r) est correct. La raison de ce mélange est que certaines propriétés des plages sont stockées et d'autres sont calculées - mais celles-ci ont changé au fil du temps et sont en fait différentes pour différents types de plages. Ce serait un meilleur style si chaque accès était du style step(r) sauf la définition de step(r) elle-même. Mais il existe des barrières psychologiques importantes contre cela. Si nous faisons r.step un appel de méthode qui vaut par défaut r..step , alors les gens peuvent simplement faire ce qu'ils sont naturellement enclins à faire.

Pour jouer l'avocat du diable (avec moi-même), devrions-nous écrire r.length ou length(r) ? L'incohérence entre les fonctions et méthodes génériques est un problème qui a affligé Python, tandis que Ruby s'est entièrement engagé dans le style r.length .

+1 pour .. comme Core.getfield !

@StefanKarpinski Cela a du sens, mais vous devrez alors ajouter une syntaxe pour les champs privés, et les interfaces devront spécifier à la fois les méthodes et les champs publics. Et en effet, vous avez besoin d'un guide de style pour assurer une certaine cohérence; le cas de length est difficile, mais il y a aussi par exemple size , qui est très similaire mais nécessite un index de dimension. Cette décision ouvre une boîte de vers ...

Dans ce cas, je supporte également .. pour accéder aux champs réels, et . pour accéder aux champs, qu'il s'agisse de méthodes ou de valeurs réelles.

Pour jouer l'avocat du diable (avec moi-même), devrions-nous écrire r.length ou length(r) ? L'incohérence entre les fonctions génériques et les méthodes est un problème qui a affligé Python, tandis que Ruby s'est entièrement engagé dans le style r.length .

Le facteur clé qui peut être sans ambiguïté pour ce problème est de savoir si vous voulez pouvoir utiliser quelque chose comme fonction d'ordre supérieur ou non. C'est-à-dire que le f en f(x) est quelque chose que vous pouvez map sur une collection, alors que le f en x.f ne l'est pas (à court d'écriture x -> x.f ) - ce qui est la même situation pour toutes les méthodes dans les langages à envoi unique.

Pourquoi s'arrêter à l'accès aux champs? Qu'en est-il d'avoir x.foo(args...) équivalent à getfield(x::MyType, ::Field{:foo}, args...) = ... ? Ensuite, nous pourrions avoir x.size(1) pour la taille le long de la première dimension. (Je ne sais pas si j'aime ma suggestion, mais peut-être quelque chose à considérer. Ou probablement pas, car les gens écriront simplement du code similaire à OO?)

Ce serait possible avec cette fonctionnalité. C'est l'une des choses qui me fait réfléchir. Je n'ai pas de problème avec un code de style oo comme celui-là - comme je l'ai dit, c'est assez agréable et les gens l'aiment vraiment - mais cela introduit suffisamment de choix dans la manière d'écrire des choses que nous avons _ vraiment_ besoin d'une politique forte sur ce que vous devriez faire puisque vous serez très libre de ce que vous pouvez faire.

Quand j'ai commencé à apprendre Julia, la syntaxe sans point m'a beaucoup aidé à abandonner mentalement le style de programmation OO. Donc, pour cette seule raison, je pense que ma suggestion est mauvaise.

Aussi, pour une surcharge simple (c'est-à-dire juste a.b sans (args...) ), je suis d'accord avec le commentaire de @nalimilan ci-dessus. Dans le numéro 4935, le consensus semble être que les champs ne devraient pas faire partie de l'API mais uniquement des méthodes; par conséquent, il semble que cette question sera close. Avoir la syntaxe . -overloading rendra beaucoup moins clair que les champs normaux ne doivent pas faire partie de l'API et encouragera probablement à intégrer les champs à l'API.

Mais oui, la syntaxe . est pratique ...

Que diriez-vous: le simple . devrait être _uniquement_ du sucre syntaxique pour getfield(x::MyType, ::Field{:name}) = ... et l'accès aux champs est _uniquement_ à travers .. (c'est-à-dire ce que . est maintenant).

Cela permettrait de faire la distinction claire:

  • le . à l'API publique d'accéder aux éléments de type valeur des instances de type
  • le .. est pour l'accès aux champs et ne doit généralement pas être utilisé dans l'API publique

Bien sûr, ce serait un changement radical.

C'est essentiellement ce que je proposais, sauf que . défaut est .. donc ça ne casse pas.

Désolé, j'aurais dû relire!

Mais je pense que . ne par défaut pas à .. pourrait en fait être bien (à part cela, il se brise), car cela obligerait le développeur à prendre une décision sur ce qu'est l'API publique et ce qui ne l'est pas. De plus, si l'utilisateur utilise un .. il peut s'attendre à ce que son code se brise, alors que . ne devrait pas.

C'est un bon point. Nous pouvons suivre cette voie en ayant a.b par défaut à a..b avec un avertissement d'obsolescence.

Du point de vue du style, je pense que je préfère de loin voir

a = [1:10]
a.length()
a.size()

que

a.length
a.size

Je pense que cela aide à préserver l'idée qu'une fonction est appelée au lieu d'une simple propriété en cours de récupération qui est en quelque sorte stockée dans le type (retour à la question de «pureté» ci-dessus). Je me demande s'il existe un moyen de garantir ce genre de style pour que les choses ne deviennent pas aussi compliquées que dans d'autres langues.

Je n'aime pas vraiment

a.length()

depuis, je ne peux pas dire s'il y avait un champ de fonction dans le type d'origine. Si . n'accède jamais aux champs, ce n'est évidemment pas un problème. Sinon, cela me semble déroutant.

A priori, je pense qu'il ne faut pas faire ni a.length() ni a.length . Mais la question est pourquoi? Qu'est-ce qui différencie r.step de r.length ? Est-ce différent? S'ils ne sont pas différents, devrions-nous utiliser step(r) et length(r) ou r.step et r.length ?

Avec la sémantique suggérée par Stefan et l'ajout de moi, il serait clair que . est toujours un appel de fonction (tout comme + aussi), alors que .. est toujours un champ accès.

Sur la question de savoir si a.length , etc est une bonne idée: que diriez-vous de . accès ne devrait être utilisé que pour accéder aux données réelles du type, plus ou moins comme on utiliserait les entrées d'un dict . Alors que nous nous en tenons aux fonctions pour les propriétés sans données telles que size , length , step etc. Parce que certains d'entre eux auront besoin de paramètres supplémentaires et, je pense, le a.size(1) type de syntaxe est mauvais.

Voici mon point de vue sur ce sujet:

  • La syntaxe du point ne doit être utilisée que pour les attributs d'un type / classe. Veuillez garder à l'esprit qu'il ne s'agit pas seulement de getters mais aussi de setters et quelque chose comme a.property() = ... semble complètement faux.
  • Bien que j'aime un peu la situation actuelle où la fonction définit l'API publique et les champs sont privés, je partage l'opinion de Stefans selon laquelle la syntaxe des points est trop belle pour être interdite pour les API publiques. Mais s'il vous plaît, limitons cela aux attributs simples. a.length est un bon exemple, a.size(1) pas parce qu'il nécessite un argument supplémentaire.
  • Veuillez laisser . par défaut à .. . Julia n'est pas connue pour être un langage standard. Laisse le garder de cette façon

Veuillez laisser . par défaut à .. . Julia n'est pas connue pour être un langage standard. Laisse le garder de cette façon

J'ai tendance à être d'accord avec cela. La syntaxe pour définir même une propriété synthétique serait simplement a.property = b , et non a.property() = b .

Bien sûr, je voulais juste expliquer pourquoi a.property() tant que syntaxe n'est pas sympa à mon humble avis

Ou plus clairement: la chose importante à propos de la syntaxe dot n'est pas que l'on puisse associer des fonctions à des types / classes mais c'est la possibilité d'écrire des getters / setters d'une manière agréable. Et les getters / setters sont importants pour l'encapsulation des données (gardez l'interface stable mais changez l'implémentation)

Ce changement serait formidable du point de vue des concepteurs d'API, mais je conviens qu'il devrait être accompagné d'une sorte de guide de style pour limiter toute incohérence future.

Cela permettrait à Ruby comme dsl ...

amt = 1.dollar + 2.dollars + 3.dollars.20.cents 

Mais soyez prêt pour java comme la folie:

object.propert1.property2.property3 ....

Quelques réflexions:

  • Je veux surtout la syntaxe . pour les dictionnaires avec symboles comme clés. C'est juste plus agréable d'utiliser d.key puis d[:key] . Mais au final, ce n'est pas critique.
  • Je pense que a->property lit mieux que a..property . Mais encore une fois, ce n'est pas si grave et je ne sais pas si cela fonctionnerait avec la syntaxe julia.

@BobPortmann Je ne suis pas d'accord. Un dictionnaire est un objet conteneur, l'API des objets conteneur est obj [index] ou obj [clé]. À l'heure actuelle, parce que nous n'avons pas de propriétés dans Julia, l'API du conteneur est surchargée pour fournir cette fonctionnalité dans des bibliothèques telles que PyCall et OpenCL. Cette modification permet de renforcer la distinction de l'API du conteneur car elle ne sera pas surchargée pour fournir des fonctionnalités supplémentaires.

Utiliser a->property pour les champs privés serait un bon moyen d'éloigner les hackers C de Julia ;-)

J'aime un peu la syntaxe .. .

La syntaxe a->property est déjà parlée - c'est une fonction anonyme. L'opérateur a..b est cependant à gagner depuis un certain temps. Dans certains cas, vous voulez quelque chose qui ressemble à un dict mais qui comporte de nombreux champs facultatifs. Utiliser la syntaxe getter / setter pour cela serait plus agréable que la syntaxe d'indexation dict.

"La syntaxe de la propriété a-> est déjà parlée - c'est une fonction anonyme."

Oui bien sûr. Cela ne ressemblait pas à cela sans espaces autour du -> .

Comme guide de style, que diriez-vous de recommander que la propriété (x) soit utilisée pour les propriétés en lecture seule et que x.property soit utilisé pour les propriétés en lecture / écriture?

Pour les propriétés inscriptibles, x.foo = bar est vraiment bien plus agréable que set_foo! (X, bar).

Avoir foo(x) pour la lecture et x.foo pour l'écriture est assez déroutant. En fait, c'est ce que les propriétés rendent si attrayantes. Avoir la même syntaxe pour l'accès en lecture et en écriture, c'est-à-dire la syntaxe la plus simple que l'on puisse obtenir (pour les getters et les setters)

En ce qui concerne le style, il y a la grande question de savoir si nous voulons avoir à la fois x.length et length(x) si cette fonctionnalité est implémentée ou si le dernier formulaire doit être obsolète et supprimé.

Mon opinion est que nous ne devrions avoir qu'une seule façon de le faire et n'utiliser que x.length à l'avenir. Et en ce qui concerne le style, je pense que c'est assez simple. Tout ce qui est une propriété simple d'un type doit être implémenté en utilisant la syntaxe de champ. Tout le reste avec des fonctions. J'ai beaucoup utilisé des propriétés en C # et j'ai rarement trouvé un cas où je ne savais pas si quelque chose devait être une propriété ou non.

Je suis contre le changement d'un ensemble de fonctions à 1 argument choisi au hasard en syntaxe x.f . Je pense que @ mauro3 a fait valoir que faire cela obscurcit la nature de la langue.

a.b est, au moins visuellement, une sorte de construction de portée. Le b n'a pas besoin d'être un identifiant globalement visible. C'est une différence cruciale. Par exemple, les factorisations matricielles avec une partie supérieure ont une propriété .U , mais ce n'est pas vraiment une chose générique --- nous ne voulons pas d'une fonction globale U . Bien sûr, c'est un peu subjectif, d'autant plus que vous pouvez facilement définir U(x) = x.U . Mais length est un autre genre de chose. Il est plus utile qu'il soit de première classe (par exemple map(length, lst) ).

Voici les lignes directrices que je suggérerais. La notation foo.bar est appropriée lorsque:

  1. foo en fait un champ nommé bar . Exemple: (1:10).start .
  2. foo est une instance d'un groupe de types liés, dont certains ont en fait un champ nommé .bar ; même si foo n'a pas réellement bar champ (1:10).step , (0.1:0.1:0.3).step .
  3. Bien que foo ne stocke pas explicitement bar , il stocke des informations équivalentes sous une forme plus compacte ou plus efficace et moins pratique à utiliser. Exemple: lufact(rand(5,5)).U .
  4. Vous émulez une API à partir d'une autre comme Python ou Java.

Il peut être judicieux que la propriété bar soit assignable dans les cas 1 et 3 mais pas 2. Dans le cas 2, comme vous ne pouvez pas changer le type d'une valeur, vous ne pouvez pas muter la propriété bar cela est impliqué par ce type. Dans de tels cas, vous voudrez probablement interdire la mutation de la propriété bar des autres types associés, soit en les rendant immuables, soit en faisant explicitement foo.bar = baz une erreur.

@tknopp , je ne suggérais pas d'utiliser x.foo pour l'écriture et foo(x) pour la lecture. Ma suggestion était que _si_ une propriété est à la fois lisible et inscriptible, alors vous voudrez probablement _à la fois_ la lire et l'écrire avec x.foo .

@StefanKarpinski : Mais length n'est-il pas un cas de 3. où les tailles sont généralement stockées et length est le produit des tailles?

Je vois cependant que Jeffs fait remarquer que ce changement rendrait ces fonctions plus de première classe.

@stevengj : Je vois. Désolé de confondre cela.

@tknopp - la longueur est dérivée des tailles, mais ne leur équivaut pas. Si vous connaissez les tailles, vous pouvez calculer la longueur, mais pas l'inverse. Bien sûr, c'est un peu une ligne floue. La principale raison pour laquelle cela est acceptable pour lufact est que nous n'avons pas trouvé une meilleure API que celle-là. Une autre approche consisterait à définir des fonctions génériques upper et lower qui donnent les parties triangulaire supérieure et triangulaire inférieure des matrices générales. Cependant, cette approche ne se généralise pas aux factorisations QR, par exemple.

Il est révélateur qu'il n'y a que quelques cas où _ vraiment_ semblent demander cette syntaxe: pycall, factorisations et peut-être des dataframes.
Je suis assez inquiet de me retrouver avec un fouillis aléatoire de f(x) vs x.f ; cela rendrait le système beaucoup plus difficile à apprendre.

Le point 1 de la liste de

Pour le moment, je peux dire quelle est l'API publique d'un module: toutes les fonctions et types exportés (mais pas leurs champs). Après ce changement, il ne serait pas possible de dire quels champs sont censés appartenir à l'API publique et lesquels non. Nous pourrions commencer à nommer des champs privés a._foo ou plus, comme en python, mais cela ne semble pas si agréable.

Personnellement, je pense que le cas DataFrames est un peu superflu. Si nous faisons cela, j'ajouterai la fonctionnalité à DataFrames, mais je trouve la perte de cohérence beaucoup plus troublante que de sauvegarder quelques caractères.

Je ne rendrais pas non plus la décision dépendante de DataFrames, PyCall (et Gtk le veut aussi). Soit nous le voulons parce que nous pensons que les champs devraient faire partie d'une interface publique (parce que cela «a l'air bien»), soit nous ne le voulons pas.

... pycall ...

et JavaCall

Puisque le cas d'utilisation principal semble être les interactions avec des systèmes non-Julia, qu'en est-il de l'utilisation de l'opérateur .. proposé au lieu de surcharger . ?

Je me demande si une solution plus simple ici est un chapeau plus général à OO:

#we already do
A[b] => getindex(A,b)
#we could have
A.b(args...) => b(A, args...)
# while
A..b => getfield(A,::Field{:b})
# with default
getfield(A, ::Field{:b}) = getfield(A, :b)

Il semble que cela permettrait à JavaCall / PyCall de faire des définitions de méthodes "dans" les classes, tout en autorisant un style général si les gens veulent avoir du code de type OO, bien que ce soit très transparent A.b() n'est qu'une réécriture. Je pense que ce serait très naturel pour les personnes venant d'OO.
Avoir également le nouveau getfield avec A..b pour permettre la surcharge, bien que la surcharge ici soit fortement déconseillée et ne soit utilisée que pour les propriétés / de type champ (je suppose que cela ne serait pas utilisé en raison de la légère peur de la surcharge getfield(A, ::Field{:field}) .

@ mauro3 :

Le point 1 de la liste de

C'était une liste de quand il est possible d'utiliser la notation foo.bar , pas quand c'est nécessaire. Vous pouvez désactiver la notation foo.bar pour les champs "privés", qui ne seraient alors accessibles que via foo..bar .

@karbarcca : Je ne suis pas très clair sur ce que vous proposez ici.

fwiw, je suis fan de l'approche des adultes consentants par convention et de rendre . totalement surchargeable. Je pense que la proposition à deux points entraînerait plus de confusion que moins.

@ihnorton - comme vous êtes contre l'utilisation de a..b comme syntaxe principale (unoverloadble) pour l'accès aux champs ou contre l'utilisation de a..b pour la syntaxe surchargeable?

L'une des meilleures caractéristiques de Julia est sa simplicité. La surcharge de x.y ressemble à la première étape sur la voie du C ++.

@StefanKarpinski mais cela signifierait alors un changement de paradigme des champs privés par défaut vers les champs publics par défaut.

Une réalisation que je viens d'avoir, c'était probablement clair pour les autres depuis le début. La programmation complète de style OO peut être effectuée avec la surcharge de base . (même si c'est moche). Définition

getfield(x::MyType, ::Field{:foo}) = args -> foofun(x, args...) # a method, i.e. returns a function
getfield(x::MyType, ::Field{:bar}) = x..bar+2                  # field access, i.e. returns a value

puis x.foo(a,b) et x.bar fonctionnent. Donc, la discussion sur la question x.size(1) savoir si x.size est sans objet.

@StefanKarpinski contre a..b généralement surchargable et tiède environ a..b -> Core.getfield(a,b) .

Je commence à voir le besoin d'un autre opérateur ici, mais a..b n'est pas tout à fait convaincant. Avoir besoin de deux personnages se sent très ... de seconde classe. Peut-être a@b , a$b ou a|b (les opérateurs au niveau du bit ne sont tout simplement pas utilisés si souvent). Une possibilité extérieure est également a b`, que l'analyseur pourrait probablement distinguer des commandes.

Je serais d'accord avec l'utilisation de l'opérateur "laid" pour l'accès primitif aux champs. Je pense que l'expérience a montré que, puisqu'il s'agit d'une opération concrète, elle est rarement utilisée, voire quelque peu dangereuse à utiliser.

Je suggère d'autoriser la simulation de la distribution unique OO par la convention / réécriture:

type Type end
# I can define methods with my Type as 1st argument
method(T, args...) = # method body
t = Type()
# then I can call that method, exactly like Java/Python methods, via:
t.method(args...)
# so
t.method(args...) 
# is just a rewrite to
method(t, args...)

La justification ici est que nous faisons déjà des réécritures de syntaxe similaires pour getindex / setindex !, donc autorisons la syntaxe OO complète avec ceci. De cette façon, PyCall et JavaCall n'ont pas à faire

my_dna[:find]("ACT")
# they can do
my_dna.find("ACT")
# by defining the appropriate find( ::PyObject, args...) method when importing modules from Python/Java

J'aime cela parce que c'est une transformation assez claire, tout comme getindex / setindex, mais permet de simuler un système OO de répartition unique si vous le souhaitez, en particulier pour les packages de langage OO.

Je suggérais alors l'utilisation de l'opérateur .. pour l'accès aux champs, avec l'option de surcharge. L'utilisation ici serait de permettre à PyCall / JavaCall de simuler l'accès aux champs en surchargeant les appels à .. , permettant aux DataFrames de surcharger .. pour l'accès aux colonnes, etc. Ce serait également le nouvel accès aux champs par défaut dans général pour tout type.

J'ai un faible pour les réécritures de syntaxe pure. C'est sans doute une mauvaise chose que vous puissiez écrire a.f(x) dès maintenant et que cela fonctionne, mais cela signifie quelque chose de déroutant différent de la plupart des langages OO.

Bien sûr, l'autre face de cette pièce est une horrible fragmentation du style, et le fait que a.f n'a rien de commun avec a.f() , ce qui fait que l'illusion s'effondre rapidement.

L'une des meilleures caractéristiques de Julia est sa simplicité. La surcharge de x.y ressemble à la première étape sur la voie du C ++.

Même sentiment ici. J'envisageais, si le besoin réel de ceci est vraiment pour un nombre limité de types d'interopérabilité, qu'en est-il de le rendre valide uniquement s'il est explicitement demandé dans la déclaration de type? Par exemple, un mot clé supplémentaire en plus de type et immutable pourrait être ootype ou quelque chose.

et le fait que af n'a rien de commun avec af (), ce qui fait que l'illusion s'effondre rapidement.

Pouvez-vous clarifier ce que cela signifie @JeffBezanson?

Je m'attendrais à ce que a.f soit une sorte d'objet méthode si a.f() fonctionne.

Ah, compris. Ouais, vous ne pourriez certainement pas faire quelque chose comme map(t.method,collection) .

Je suis d'accord avec @ mauro3 qu'en autorisant obj.method(...) , il y a un risque que les nouveaux utilisateurs voient simplement Julia comme un autre langage orienté objet essayant de rivaliser avec python, ruby, etc., et ne pas apprécier pleinement la génialité qui est l'envoi multiple. L'autre risque est que le style oo standard devienne alors prédominant, car c'est ce avec quoi les utilisateurs sont plus familiers, par opposition au style plus julien développé jusqu'à présent.

Étant donné que le cas d'utilisation, autre que DataFrames, est limité à l'interopérabilité avec les langages oo, cela pourrait-il être géré par des macros? ie <strong i="8">@oo</strong> obj.method(a) devient method(obj,a) ?

@karbarcca cela signifierait que tout pourrait automatiquement être écrit de deux manières:

x = 3
x.sin()
sin(x)
x + 2
x.+(2) # ?!

@karbarcca https://github.com/JuliaLang/julia/issues/1974#issuecomment -38830330

t.method (args ...)
# est juste une réécriture de
méthode (t, args ...)

Cela ne serait pas nécessaire pour PyCall puisque le point surchargeable pourrait simplement être utilisé pour appeler pyobj[:func] par pyobj.func . Alors pyobj.func() serait en fait (pyobj.func)() .

Réécrire a.foo(x) comme foo(a, x) ne résoudrait pas le problème de PyCall, car foo n'est pas et ne peut pas être une méthode Julia, c'est quelque chose que je dois rechercher dynamiquement au moment de l'exécution . J'ai besoin de réécrire a.foo(x) comme getfield(a, Field{:foo})(x) ou similaire [ou éventuellement comme getfield(a, Field{:foo}, x) ] pour que mon getfield{S}(::PyObject, ::Type{Field{S}}) puisse faire ce qu'il faut.

@JeffBezanson https://github.com/JuliaLang/julia/issues/1974#issuecomment -38837755

Je commence à voir le besoin d'un autre opérateur ici, mais a..b n'est pas tout à fait convaincant. Besoin de deux personnages se sent très ... de seconde classe

Je dirais que, d'un autre côté, .. est tapé beaucoup plus rapidement que $ , @ ou | car aucune touche shift ne doit être pressée , et tout en étant deux caractères, le doigt reste sur la même touche: sourire:

@stevengj Ah, je vois. Mais mon argument tient toujours, que la réécriture pourrait être faite avec une macro.

Pour JavaCall, je n'ai en fait besoin que d'un gestionnaire unknownProperty. Je n'ai pas vraiment besoin de réécrire ou d'intercepter la propriété existante en lecture ou en écriture. Une règle selon laquelle "ax est réécrit dans getfield (a,: x) uniquement lorsque x n'est pas une propriété existante" aiderait-elle à garder les choses saines?

@simonbyrne , exiger une macro irait à l' appels inter - type Foo; p::PyObject; end , et pour un objet f::Foo vous voulez faire foo.p.barbar est une recherche de propriété Python. Il est difficile d'imaginer une macro qui pourrait distinguer de manière fiable la signification des deux points dans foo.p.bar .

Honnêtement, je ne vois pas le gros problème du style. Les packages de haute qualité imiteront le style de Base et d'autres packages lorsque cela est possible, et certaines personnes écriront du code étrange quoi que nous fassions. Si nous mettons la surcharge de points dans une section ultérieure du manuel, et recommandons son utilisation uniquement dans quelques cas soigneusement sélectionnés (par exemple l'interopérabilité inter-langue, les propriétés de lecture / écriture, peut-être pour éviter la pollution des espaces de noms pour des choses comme factor.U , et en général comme alternative plus propre à foo[:bar] ), alors je ne pense pas que nous serons submergés par des paquets utilisant dot pour tout. L'essentiel est de décider ce que _nous_ utiliserons et de recommander, et nous devrions probablement garder la liste des utilisations recommandées très courte et ne l'étendre que lorsque les besoins du monde réel se présentent.

Nous n'ajoutons pas de syntaxe de type OO super facile comme type Foo; bar(...) = ....; end pour foo.bar(...) , ce qui limitera également la tentation des débutants.

Je suis fondamentalement en plein accord avec @stevengj ici. J'aime a..b pour un accès réel au champ car il

  1. ressemble à a.b
  2. est moins pratique, comme il se doit
  3. est seulement _légèrement_ moins pratique
  4. n'a aucune signification existante et nous n'en avons trouvé aucune utilisation convaincante depuis plus d'un an
  5. n'est pas terriblement bizarre comme a b`

Avec ce changement et éventuellement (https://github.com/JuliaLang/julia/issues/2403), presque toute la syntaxe de Julia sera-t-elle surchargée? (L'opérateur ternaire est la seule exception à laquelle je puisse penser) Le fait que presque toute la syntaxe soit abaissée à une répartition de méthode surchargeable me semble être une caractéristique fortement unificatrice.

Je conviens que c'est en fait une sorte de simplification. L'opérateur ternaire et && et || contrôlent vraiment le flux, donc c'est un peu différent. Bien sûr, ce genre d'arguments contre le fait a..b faire de

Oh, il y a aussi un appel de fonction qui n'est pas surchargeable. Si basique que je l'ai oublié.

C'est à cela que répond le numéro 2403.

Oui. Mais c'est beaucoup plus près de se produire que cela ne l'est.

Le seul inconvénient pour moi ici est que ce serait vraiment bien d'utiliser le véritable opérateur d'accès au champ pour les modules, mais cela n'arrivera probablement pas car personne ne veut écrire Package..foo .

Remplir les tabulations après les points devient un peu moche; techniquement, vous devez vérifier quelle méthode x. peut appeler pour voir s'il est approprié de lister les noms de champs d'objets ou de modules. Et j'espère que personne n'essaiera de définir getfield(::Module, ...) .

Je pense que le remplissage des onglets peut être fait comme ceci: foo.<tab> liste les "champs publics" et foo..<tab> répertorie les "champs privés". Pour les modules, serait-il correct d'autoriser simplement l'implémentation par défaut de Mod.foo être Mod..foo et de dire aux gens de ne pas ajouter de méthodes getfield à Module ? Je veux dire, vous pouvez déjà redéfinir l'addition d'entiers dans le langage - tout l'enfer se déchaîne et vous obtenez un segfault mais nous n'essayons pas de l'empêcher. Cela ne peut pas être pire que ça, n'est-ce pas?

C'est en fait un peu pire que cela, car un langage de programmation ne se soucie vraiment que de la dénomination. La résolution des noms est beaucoup plus importante que l'ajout d'entiers.

Nous n'avons pas beaucoup d'autre choix que d'avoir Mod.foo par défaut à Mod..foo , mais nous devrons probablement utiliser Mod..foo pour le bootstrap à certains endroits. L'opérateur .. est extrêmement utile ici, car sans lui, vous ne pouvez même pas appeler Core.getfield pour définir le repli. Avec lui, nous supprimerions probablement simplement Core.getfield et n'aurions que .. .

C'est un bon point - le nommage est un gros problème dans la programmation :-). Cela semble être une bonne façon de procéder - seulement .. et non Core.getfield .

Ces deux idées,

[...] placez la surcharge de points dans une section ultérieure du manuel et recommandez son utilisation uniquement dans quelques cas soigneusement sélectionnés @stevengj https://github.com/JuliaLang/julia/issues/1974#issuecomment -38847340

et

[...] la préférence doit être d'utiliser autant que possible la syntaxe x.property https://github.com/JuliaLang/julia/issues/1974#issuecomment -38694885

sont clairement opposés.

Je pense que si la première idée doit être choisie, il est plus logique de créer un nouvel opérateur .. pour ces "cas soigneusement sélectionnés".
Comme avantage, utiliser ..name pour les cas où [:name] est actuellement utilisé (DataFrames, Dict {Symbol, ...}) serait plus convivial pour la saisie / syntaxe tout en indiquant clairement que quelque chose de différent de l'accès aux champs arrivait. De plus, le double point dans ..name pourrait être vu comme un deux-points tourné, un indice sur la syntaxe du symbole :name , et il n'y aurait pas non plus de problème avec les complétions de tabulations.
Comme inconvénient, les utilisations de PyCall et al. ne serait pas si proche des syntaxes originales (et pourrait même être déroutant pour les cas où le . doit vraiment être utilisé). Mais soyons honnêtes, Julia ne sera jamais entièrement compatible avec la syntaxe Python, et il y aura toujours des cas où l'on devra taper beaucoup dans Julia avec PyCall pour exécuter des instructions autrement simples en Python. Le .. à émuler . pourrait donner un bon équilibre ici. (Ne vous méprenez pas, j'aime vraiment PyCall et je pense que c'est une fonctionnalité critique qui mérite une attention particulière)

La deuxième idée, que je préfère actuellement, a la grande décision quant au moment où property(x) ou x.property doit être utilisé, ce qui nécessite une définition élégante, bien que claire, si une telle chose existe. .
Il semble que si les gens veulent un . surchargable, c'est parce qu'ils préfèrent le style API x.property en premier lieu.
Quoi qu'il en soit, je préférerais voir . non pas comme un opérateur d'accès de champ surchargeable mais comme un opérateur d'accès de "propriété" surchargeable ( getprop(a, Field{:foo}) peut-être?) Qui par défaut est un opérateur de champ non surchargeable .. .
D'autres décisions devraient également être prises, par exemple, qui seront utilisées dans le code d'implémentation concret pour l'accès au champ, .. ou . ? Par exemple, pour l'exemple de l'étape Ranges, lequel sera idiomatique? step(r::Range1) = one(r..start) ou step(r::Range1) = one(r.start) ? (sans parler de la question de savoir si step doit être une méthode ou une propriété).

C'est pourquoi j'ai reculé sur cet angle et proposé ces critères: https://github.com/JuliaLang/julia/issues/1974#issuecomment -38812139.

Juste une pensée qui m'est venue à l'esprit en lisant ce fil intéressant. L'export peut être utilisé pour déclarer des champs publics, tandis que tous les champs sont visibles à l'intérieur du module de définition, par exemple:

module Foo
   type Person
     name
     age
   end
   export Person, Person.name
   <strong i="6">@property</strong> Person :age(person) = person..age + 1
end

Dans cette situation, la personne exportée ressemble toujours à «nom» et «âge», sauf que dans ce cas, l'âge est en lecture seule via une fonction qui en ajoute un. L'exportation de toute la personne peut être effectuée en tant que personne d'exportation * ou similaire.

[pao: citations]

@emeseles Veuillez faire attention à utiliser des contre-indications pour citer des éléments qui ressemblent à du code Julia - cela garantit que le formatage est conservé et empêche les macros de Julia de créer des notifications GitHub pour les utilisateurs portant le même nom.

. et .. sont déroutants: un sintax clair et facile à retenir est quelque chose de bien

J'ai vraiment hâte de pouvoir le faire. Est-ce un changement assez important pour qu'il (ou le WIP dans # 5848) soit signalé comme projet 0.4?

Oui, c'est définitivement un projet.

Je pense que la plupart d’entre nous conviennent que ses usages recommandés devraient être limités, du moins pour commencer. Mon sentiment est qu'il ne devrait être initialement recommandé que pour deux usages: l'interopérabilité (avec d'autres langages, comme dans PyCall, et plus généralement pour les bibliothèques externes où la notation par points est naturelle), et peut-être pour les objets à état mutable (depuis get_foo(x) et set_foo!(x, val) sont moche).

Même si nous ne le recommandons que pour l'interopérabilité des appels étrangers, cet objectif seul suffit à mon avis à justifier cette fonctionnalité. Pour un nouveau langage comme Julia, parler en douceur avec le reste de l'univers logiciel est extrêmement important.

Steven, je ne suis pas sûr à 100% du getter / setter car je crains que cela conduise bientôt à des incohérences mais je suis d'accord avec l'autre cas d'utilisation. En plus de cela, nous avons dans Gtk.jl des propriétés dynamiques qui bénéficieraient également de la syntaxe. Mon préféré est l'implémentation enum que Stefan a décrite dans # 5842.

Bosse. Qu'est-ce qui bloque les progrès sur cette question? Une décision est-elle nécessaire, ou ce problème dépend-il d'autres changements internes, pas encore fait, ou s'agit-il simplement d'un codage?

Qu'est-ce qui bloque les progrès sur cette question?

Quelqu'un qui fait le travail et hésite à savoir si c'est la bonne chose à faire.

Notez que @ihnorton a déjà effectué une mise en œuvre préliminaire au # 5848. Je pense que le travail a stagné principalement parce qu'une déclaration claire de l'équipe de base de Julia est nécessaire pour savoir s'il s'agit d'une fonctionnalité souhaitée.

Je suis d'accord avec ça. @JeffBezanson semble être sur la clôture.

Pour moi, avoir cette fonctionnalité faciliterait la transition de notre grande base de code Python vers Julia. Pour expliquer aux étudiants, s'ils utilisent du code Python, ils ont besoin d'une syntaxe assez différente de celle à laquelle ils sont habitués, cela peut devenir difficile.

Nous avons eu cette discussion ci-dessus dans ce fil et je ne vois toujours pas d'accord complet. Actuellement, plusieurs personnes pensent qu'une API publique est constituée de fonctions / méthodes tandis que l'API privée est constituée de champs de type composite. Je peux voir de très rares exceptions à ce schéma. ( .U dans une décomposition LU?)

Cela ne signifie pas que je suis contre cela car l'accès et les énumérations Python sont des cas où cela est utile. On peut encore se demander à quel point le besoin est urgent et s'il serait sage de le pousser à la fin d'un cycle de développement.

@ ufechner7 , je suis d'accord que la motivation principale est l'interopérabilité inter-langues. @tknopp , nous dépend de ce que @StefanKarpinski décident.

Je pense qu'une grande partie de l'hésitation provient de ce que j'imagine être le pire cauchemar de Jeff:

module DotOrientedProgramming
  Base.getfield(x, ::Field{:size}) = size(x)
  Base.getfield(x, ::Field{:length}) = length(x)
  ⋮
end

Je n'aimerais pas du tout cela aussi - tout paquet qui décide de l'utiliser à mauvais escient comme celui-ci imposera son utilisation abusive à tous les types du système, y compris le mien. Cette fonctionnalité est très puissante et changera la façon dont Julia est écrite. Pour le meilleur et (peut-être, mais espérons-le pas) pour le pire.

Oui, bien sûr Steven, cela peut ne pas être correctement formulé de ma part. Le point que je voulais souligner est que ce changement peut avoir une influence majeure sur la façon dont la langue va évoluer. Et l'idée d '"interface formelle" que nous avons dans un autre problème est également influencée en rendant . surchargeable. Alors oui, laissons @JeffBezanson et @StefanKarpinski décider. La question est toujours de savoir si la décision doit être exécutée maintenant ...

Pour ce que ça vaut, je suis venu en faveur de rendre presque toutes les syntaxes surchargables, puis de compter sur la culture pour résister à devenir trop sauvage avec elle.

+1 . Je pense qu'il y a un fort analogue philosophique (et peut-être pratique ...) à la surcharge call ici. Le manuel a besoin d'une section intitulée Don't do stupid stuff: we won't optimize that . (bien sûr, la surcharge de call était en partie _pour_ des raisons de performances - mais elle regorge de risques d'abus)

  • : 100: à cela. Je pense que la culture devrait être une motivation suffisante pour ne pas en abuser

Dans l'ensemble, je suis en faveur. Le potentiel d'abus n'est pas ma plus grande inquiétude. Pour moi, les gros problèmes sont

  • Syntaxe acceptable pour l'accès aux champs "réels". Je n'aime pas trop a..b .
  • Modules. Les noms qualifiés sont extrêmement importants. Les exprimer avec une méthode d'expédition est possible, mais présente des difficultés pratiques. À savoir, vous devez passer par de nombreuses phases de compilation (peut-être même par inlining) juste pour savoir que vous avez un nom qualifié. Cela rend la vie plus difficile pour quiconque écrit des outils qui consomment des AST. Cela rend également très facile de gérer ce cas de manière incorrecte dans de tels outils.

Ces problèmes pourraient être résolus en un seul coup en utilisant la même syntaxe pour les deux, mais il est presque impossible d'imaginer utiliser autre chose que . pour les modules à ce stade. _Internally_ il y aura certainement une syntaxe abstraite pour les références de module; ce serait frustrant s'il n'y avait pas de bonne façon d'exposer cela.

Mes deux cents sur cette question: pourquoi ne pas utiliser : pour les noms qualifiés? Il est déjà utilisé pour quelque chose de similaire:

import Base: call, show, size

Cela donnerait quelque chose comme

module Foo
    module Bar
        f(x) = 3*x
    end
    const a = 42
end

<strong i="10">@assert</strong> Foo:a == 42

Foo:Bar:f(789)

Ou est-ce qu'ils utilisent déjà trop souvent le symbole : ? Le symbole :: (style C ++) me semble trop verbeux.

Le : est déjà le symbole le plus surchargé de Julia, donc ça ne va pas aider, j'en ai peur.

Pouvons-nous simplifier le problème de nommage qualifié en rendant module.name non surchargeable? Puisque les liaisons de module sont constantes, cela nous permettrait de garder la même sémantique mais de court-circuiter toute la logique normale pour les recherches de noms qualifiés dès que l'on sait que le LHS de a.b est un module. Je pense qu'il est assez raisonnable de ne pas permettre aux gens de remplacer ce que signifie rechercher un nom dans un module.

J'aime plutôt la syntaxe a..b pour un accès réel aux champs. Quelle est votre objection?

A part: J'aurais aimé que nous ayons choisi ( ) pour les listes d'importation comme certains langages fonctionnels. C'est à dire:

import Base (call, show, size)

Ma raison est que nous pourrions rendre les virgules facultatives et autoriser les virgules de fin. Cela me dérange vraiment que tous les noms importés aient besoin de virgules à la fin, sauf le dernier qui ne peut pas en avoir.

Oui, j'étais sur le point de mentionner la possibilité de faire en sorte que a.b signifie "si a est un module, alors faites d'abord la recherche du module". Cela pourrait aider, et nous ne voulons certainement pas remplacer la signification de la recherche de module. Cela a un coût de complexité cependant, puisque nous ne pouvons pas représenter a.b comme l'appel getfield(a,:b) . Il doit s'agir d'un nœud AST spécial avec une branche implicite. Bien sûr, nous pourrions utiliser une branche _explicit_, mais je m'inquiéterais du gonflement AST de cela.

Il ne semble pas y avoir de moyen facile de sortir d'un conflit aussi énorme entre les besoins du front-end et du back-end.

Si tout le monde aime a..b je suppose que je peux apprendre à vivre avec. Il me semble que cela signifie quelque chose de totalement différent, un intervalle peut-être.

Je n'aime pas non plus a..b mais je me demande pourquoi cela serait nécessaire. En lisant ce fil, on a l'impression que la surcharge ne sera utilisée que dans les wrappers de langage et les cas d'utilisation dynamiques où l'accès réel au champ n'est pas requis.

Parce qu'à un moment donné, vous devez accéder à la représentation d'un objet pour pouvoir en faire quelque chose. On pourrait dire que ce serait relativement rare, et donc peut être moche comme get_actual_field(a,:x) , mais cela semble être une opération trop importante pour ne pas avoir de syntaxe.

Je vois cela, mais cela semble que nous cherchons une syntaxe que nous voulons que personne n'utilise, n'est-ce pas?

Ne pas fournir .. serait un moyen de dire oui pour les cas d'utilisation dynamiques mais non pour la programmation orientée point

Je ne vois pas comment cela empêcherait la programmation orientée point; vous pouvez toujours faire l'exemple de @mbauman ci-dessus.

Alors que la syntaxe a..b ressemble en quelque sorte à un intervalle (je l'ai utilisée comme telle), je ne pense tout simplement pas que l'arithmétique des intervalles ait besoin de sa propre syntaxe d'entrée - écrire Interval(a,b) est juste très bien et personne ne veut utiliser cette syntaxe pour grand-chose, car c'est un opérateur dans Julia depuis des années maintenant et personne ne l'utilise pour beaucoup de choses. Cela ressemble aussi à un accès sur le terrain.

Nous pouvons remplacer le hideux module_name par m..name . Ne pas pouvoir accéder aux champs des objets Module a été une verrue.

Oui, j'étais sur le point de mentionner la possibilité de faire en sorte que a.b signifie "si a est un module, alors faites d'abord la recherche du module". Cela pourrait aider, et nous ne voulons certainement pas remplacer la signification de la recherche de module. Cela a un coût de complexité cependant, puisque nous ne pouvons pas représenter a.b comme l'appel getfield(a,:b) . Il doit s'agir d'un nœud AST spécial avec une branche implicite. Bien sûr, nous pourrions utiliser une branche _explicit_, mais je m'inquiéterais du gonflement AST de cela.

Pourrions-nous gérer cela en faisant que a.b signifie inconditionnellement getfield(a,:b) , puis en faisant une erreur pour ajouter des méthodes à getfield qui croisent la méthode getfield(::Module, ::Field) ? C'est en quelque sorte une façon étrange d'imposer ce comportement, mais cela aurait finalement le même effet. Ensuite, la réduction pourrait simplement utiliser le fait que nous savons que vous ne pouvez pas faire cela pour tricher et réduire module.name à la recherche de nom qualifié.

Ok, je dis l'inverse: est-ce que quelqu'un dans ce fil utiliserait .. et si oui, quel serait un cas d'utilisation exemplaire? (c'est-à-dire que l'observation complète de l'accès au champ interne serait ok)

@StefanKarpinski Oui, cela pourrait fonctionner. Pourrait être un autre cas où nous voulons une sorte de méthodes «scellées».

@tknopp Accès à module..name et module..parent :) Aussi, juste pour clarifier, préconisez-vous une syntaxe d'appel de fonction comme get(obj,:field) pour l'accès aux champs de bas niveau?

Non, je ne préconise pas une certaine syntaxe. Je pense simplement qu'il serait bon de savoir pourquoi cette fonctionnalité est nécessaire et quels sont les cas d'utilisation. Pour les cas d'utilisation dynamiques, il serait normal que

  • a.b est un accès au champ si Base.getfield(a, ::Field{:b}) n'a pas été défini
  • a.b est la version dynamique si Base.getfield(a, ::Field{:b}) est défini. Dans ce cas, l'accès réel au champ pourrait être masqué

Ma question était de savoir s'il existe des cas d'utilisation où l'observation n'est pas acceptable.

Oui; vous pouvez définir pyobject.x pour que x soit toujours recherché dans le dictionnaire du pyobject, pour tout x . Ensuite, un mécanisme séparé est nécessaire pour accéder aux champs julia du pyobject.

Ahhh, alors c'est tout ou rien? J'ai en quelque sorte l'impression qu'on aurait pu

type A
  c
end

Base.getfield(a::A, ::Field{:b}) = 3

a = A(1)

a.c # This still calls the field access
a.b # This calls the function

Oui, vous _pouvez_ faire cela, mais tous les objets ne le feront pas. Certains voudront définir getfield(a::A, ::Field) pour intercepter tous les champs.

Ok merci maintenant je comprends. Tous les cas d'utilisation dynamiques voudraient getfield(a::A, ::Field) et auraient donc besoin d'un moyen quelconque d'appeler les champs internes.

Ensuite, je pense que Core.getfield est suffisant à moins que quelqu'un ne trouve un cas d'utilisation pratique où cela est ennuyeux.

C'est probablement une donnée, mais nous allons également autoriser le remplacement de setfield! , non? J'aimerais vraiment cela pour exposer des vues mutables dans une base de données dans laquelle les lignes deviennent des types.

Oui, c'était mon impression.

Ok, IMHO utiliser .. pour un accès réel au champ ou Core.getfield n'est pas si grave. On pourrait introduire la caractéristique générale comme expérimentale et la faire évoluer.

La question est de savoir si cela s'inscrira ou non dans le délai de 0,4. Donc, il # 5848 proche de la mise en œuvre finale et le module peut-il être résolu?

@johnmyleswhite : Je voterais aussi pour rendre cela symétrique et autoriser également setfield! . Dans Gtk.jl, nous utiliserions les deux.

Il ne semble pas très clair quelle serait la règle quand utiliser cette fonctionnalité et quand ne pas l'utiliser. Je vois le point pour PyCall, où une méthode / un champ doit être recherché dynamiquement et ne peut donc pas être une méthode / un type composite Julia (et la syntaxe résultante est plus proche de Python). Mais alors, pourquoi l'utiliser pour Gtk.jl? S'il commence à faire foo.bar = x au lieu de setbar!(foo, x) , alors le code Julia standard commencera invitablement à utiliser ce modèle aussi: est-ce ce que nous voulons? C'est peut-être le cas, mais soyons clairs à ce sujet.

Serait-il acceptable / recommandé d'utiliser cette fonctionnalité pour implémenter des getters et des setters de propriété définis pour les types abstraits (et concrets aussi)?
Je suppose que cela permettrait d'éviter le conflit de noms entre les méthodes utilisées pour obtenir des propriétés de différents types de modules différents.

Réf .: https://github.com/JuliaLang/julia/issues/4345 , https://groups.google.com/forum/#!msg/julia -users / p5-lVNlDC8U / 6PYcvvsg29UJ

@nalimilan : Gtk a un système de propriétés dynamique qui ne concerne pas les getters / setters.

@tknopp Ah, d'accord, en effet. Mais pour les propriétés les plus courantes, vous avez une fonction getter / setter (rapide), plus la propriété dynamique. Alors recommanderiez-vous d'utiliser la fonction getter / setter lorsqu'elle est disponible, et la syntaxe de surcharge de champ uniquement pour les propriétés qui n'en ont pas? Cela me semble bien - mais il est bon d'avoir une politique claire à propos de cette IMHO.

À mon avis, à ce stade (et je pense que nous devons expérimenter un peu cela pour trouver les bonnes règles), f(x) est meilleur lorsque f un sens en tant que concept général autonome comme " length "tandis que x.f doit être utilisé lorsque f n'est pas vraiment indépendant de x . Pour essayer d'adapter mon exemple précédent à cela, il n'est pas vraiment utile d'avoir une fonction générique step car la plupart des vecteurs et des collections n'ont aucune sorte de notion d'étape - cela n'a de sens que lorsque vous avez une plage de quelques sortes. Ainsi, il est normal que x.step soit le moyen d'obtenir le pas d'une plage x . C'est un peu une question de jugement, mais je suppose que la vie en est pleine.

Je n'aime pas .. car il ne me donne pas d'accès direct. Que diriez-vous de foo.bar. Le point supplémentaire à la fin des broches pour un accès direct.

Pourrait également choisir un symbole unicode: il en reste encore beaucoup ...

@GlenHertz , cela ne fonctionne pas vraiment si vous devez enchaîner les accès aux champs.

@simonbyrne , je suis généralement contre le fait d'avoir quoi que ce soit dans le langage de base ou dans la bibliothèque standard qui nécessite l'utilisation d'Unicode. Le permettre est une chose, forcer les gens à l'utiliser en est une autre.

À mon avis, à ce stade (et je pense que nous devons expérimenter un peu cela pour déterminer les bonnes règles), f (x) est meilleur lorsque f a un sens en tant que concept autonome général comme "longueur" alors que xf devrait être utilisé quand f n'est pas vraiment indépendant de x.

Ma règle personnelle pour utiliser cette fonctionnalité va être: ne l'utilisez que pour l'interopérabilité de la langue ou pour des choses qui sont soit "presque des champs", soit des "champs augmentés". Par exemple, je pourrais l'utiliser pour mettre à jour un cache qui dépend de la valeur de tous les champs d'un type.

Une grande question que j'ai à propos de cette fonctionnalité: comment cela interagit-il avec l'inférence de type? Il semble que vous soyez sur le point de définir une fonction getfield(x::T, s::Symbol) qui produit une sortie typée différemment pour différentes valeurs de s . Cela fonctionne-t-il uniquement parce que getfield est magique? Pouvez-vous redéfinir la sortie de getfield(x, s) pour fixe x et s à tout moment dans un programme? Si tel est le cas, comment cela s'accorde avec l'incapacité de redéfinir un type?

Il semble que vous soyez sur le point de définir une fonction getfield(x::T, s::Symbol) qui produit une sortie typée différemment pour différentes valeurs de s .

C'est pourquoi le plan est d'exprimer cela comme getfield{s}(x::T, f::Field{s})s est un symbole.

J'avais raté ça. Merci de m'avoir corrigé.

@nalimilan : Oui, les champs surchargés ne seraient utilisés que pour les propriétés dynamiques. C'est ainsi que Jameson veut s'attaquer à cela et je pense que c'est bien. Tous les vrais getters et setters sont générés automatiquement mais fonctionnent toujours sans tous les noms get / set. Le live dans le module GAccessor (court G_ )

Sur la syntaxe, pourquoi ne pas utiliser <- pour un accès réel aux champs?
C'est similaire à -> en c ++ qui est utilisé pour les lamdas mais <- est actuellement inutilisé.
Il pourrait être lu à partir du type, obtenir la valeur directement.

Il laisserait .. inutilisé pour ceux qui veulent l'utiliser à des intervalles encore et utiliserait une paire inutilisée qui n'a pas d'autres utilisations auxquelles je peux penser jusqu'à présent.

N'utilisons pas la notation d'affectation de R pour l'accès aux champs.

Nous pourrions éventuellement utiliser -> pour refléter directement C / C ++ et obtenir une nouvelle syntaxe pour
fonctions anonymes. Je n'ai jamais beaucoup aimé la fonction anonyme
syntaxe car c'est un peu laconique / illisible. Peut-être que nous pourrions plutôt faire
quelque chose comme

func (x) x ^ 2 fin

ou le plus long, le plus cohérent

function (x) x ^ 2 fin

Il y aurait peut-être un moyen de trouver une bonne syntaxe qui ne nécessite pas
en utilisant une extrémité.

Ne pas trop changer la discussion, mais cela aurait certainement du sens
à utiliser -> pour un accès réel sur le terrain.

Le mer 28 janvier 2015 à 8 h 49, John Myles White [email protected]
a écrit:

N'utilisons pas la notation d'affectation de R pour l'accès aux champs.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -71857083.

@quinnj : func (x) x^2 end fonctionne déjà. Mais c'est bien d'avoir une syntaxe très concise pour les fonctions anonymes: map(x->x^2, 1:10)

Je ne pense pas que l'accès aux champs nécessite une syntaxe spéciale (un caractère unicode comme @simonbyrne suggéré est acceptable comme option). Je ne voudrais pas perdre x -> x^2 .

Il semble que ce problème soit toujours ouvert / en attente de discussion? J'ai été intéressant de lire tous les différents commentaires ici sur l'opérateur point.

Y a-t-il eu des suggestions pour ajouter d'autres nouveaux jetons d'opérateur? Utiliser quelque chose comme :> pourrait être une bonne alternative. Il a des parallèles avec |> et pourrait avoir une Julia _feel_ plus native:

c = foo.a + foo.b
pyobj:>obj:>process(c)

Tout en étant beaucoup plus facile à écrire que:

pyobj[:obj][:procees](c)

Ou en comparant:

someobj :> array |> length 
# vs
length(get_array(someobj)) 

Je suis nouveau chez Julia, mais j'ai rapidement acquis une forte appréciation pour l'approche de répartition multiple. La programmation orientée objet - en particulier pour la programmation scientifique - rend beaucoup de tâches plus lourdes. Je crains que le paradigme / syntaxe OO n'affecte négativement le développement du style de Julia.

Ou bien, depuis que j'ai oublié le stage de chaîne et / ou la citation:

someobj <: field_name |> length

@elcritch , <: est actuellement utilisé comme l'opérateur "est le sous-type de" de Julia, et si :> est introduit, il est probable que nous souhaitons l'utiliser pour quelque chose de type à cause de cela patrimoine.

Si l'utilisation de instance..member pose problème, voici quelques possibilités. Protégez vos yeux! Il est probable que chacun d'entre eux soit pire:

  • instance^.member (point de carotte)
  • instance~.member (point tilde)
  • instance:.member (deux points)
  • instance*.member (point étoile)
  • instance-.member (tiret point)
  • [email protected] (point du signe arobase)
  • instance&.member (ampère point)
  • instance$.member (point en dollars)
  • instance<.>member (point de vaisseau spatial)

Je pense honnêtement que (a) .. semble assez bien et (b) peu importe que cela soit beau parce que ce sera toujours un coin obscur du langage. La plupart des gens utiliseront instance.member car ils n'auront qu'un champ ou une méthode getfield , mais pas les deux.

(D'ailleurs, la plupart des gens qui veulent utiliser à la fois un membre et une méthode ne prendront probablement même pas la peine d'en savoir plus sur .. . Ils définiront simplement une méthode pour foo.member et nommeront le "réel "field foo._member . On peut dire que c'est de toute façon un meilleur style - cela signifie que lorsque vous lisez la définition de type , il sera immédiatement évident que _member n'est pas censé être quelque chose vous pouvez ou devriez y accéder directement. Cela plaiderait pour faire de .. quelque chose de laid et d'obscur comme :. plutôt que d'acquérir de précieux biens immobiliers de ponctuation.)

Je manquerais la possibilité d'utiliser .. comme opérateur d'intervalle d'infixe, mais l'accès aux champs surchargeables est un compromis intéressant. Bien que j'hésite à être légèrement terrifié :. ne semble pas si mal.

Notez que :. est en fait une syntaxe valide pour symbol(".") ce moment, donc ce n'est peut-être pas bon de l'utiliser. Le point que .. est potentiellement utile est bien compris; nous ne devrions pas le gaspiller sur une syntaxe que personne n'utilisera. Je serais parfaitement heureux d'aller avec quelque chose d'encore plus laid comme @. (puisque . n'est pas un nom de macro valide et ne peut pas commencer un identifiant, cela ne semble pas entrer en conflit avec quoi que ce soit ). Encore une fois, cela va être un coin tellement obscur de Julia qu'il ne vaut pas la peine d'essayer de le rendre joli.

+1 pour simplement faire cela en utilisant .. et en ignorant toute laideur potentielle

Ouais, allons-y pour .. toute façon s'il pouvait être utilisé pour .. alors je pense que ce serait un constructeur _range_, mais bon c'est déjà là avec deux points par exemple. start:stop .

Un dernier punt: qu'en est-il de .: ? Est-ce trop subtil d'avoir ab, a. (B), a. (: B) _et_ a.:b?

@hayd , cela semble trop subtil et facile à utiliser par accident.

@ihnorton , y a-t-il une chance de ressusciter une version de # 5848? Nous pourrions lancer la question de syntaxe et utiliser simplement Core.getfield(x, Field{y}) pour accéder au champ "réel".

Mis à part la syntaxe de Core.getfield , reste-t-il des questions de fond?

Dans # 5848, @tknopp a suggéré de ne rendre surchargable que l'accès aux champs "réels", contrairement à la suggestion de x::Vector{Any} , alors faire x[i].y peut être interprété getfield(x[i], Field{:y}) et le système de répartition fera la bonne chose indépendamment du fait que y est un champ réel, alors que si vous voulez seulement appeler getfield pour les champs "virtuels", le codegen devra implémenter un sous-ensemble miniature du système de répartition pour la vérification d'exécution du x[i] type.

Une autre question était de savoir si Module.foo devrait être surchargeable. D'une part, il y a une certaine cohérence à utiliser getfield pour tout, et l'exemple Vector{Any} mentionné ci-dessus pourrait avoir des membres de tableau Module , donc nous devrons gérer ce cas en tous cas. D'un autre côté, @JeffBezanson a souligné que cela pourrait rendre la compilation plus difficile et rendre le comportement de déclarations comme function Base.sum(...) difficile à gérer. Ma préférence serait de rendre Module.foo non surchargeable, du moins pour l'instant, dans tous les cas où le compilateur sait qu'il fonctionne avec un Module (c'est-à-dire pas un Vector{Any} ) ; la légère incohérence semble en valoir la peine afin d'être prudent sur ce qui est changé.

+1 pour ne pas autoriser la surcharge de Module.foo .

Pour marquer ici, un domaine du calcul scientifique où la programmation et la syntaxe OO sont en fait supérieures à la FP est la modélisation basée sur les agents. Bien que l'héritage concret et multiple me manque pour mettre en place des hiérarchies d'agents, les abstractions légères et rapides et le prototypage rapide de Julia sont étonnants - Déjà quelques frameworks ABM sont apparus.

Dans ABM, la notation par points est préférable pour exprimer les interactions d'agent: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Cette évidence n'est pas le cas d'utilisation le plus important, mais il serait bien de conserver ce sucre syntaxique pour réfléchir et coder sur les ABM.

J'aimerais aussi beaucoup que cette syntaxe soit disponible dans Julia. Comme
autant que j'apprécie l'approche orientée fonction d'un design
perspective, la syntaxe d'appel de méthode est très utile et lisible dans plusieurs
domaines. Ce serait formidable si Ab (C) était équivalent à b (A, C).
Le 22 avril 2015 à 8h50, "datnamer" [email protected] a écrit:

Carillon ici, un domaine du calcul scientifique où la programmation OO
et la syntaxe est en fait supérieure à FP est la modélisation basée sur les agents. Bien que je
manque d'héritage concret et multiple pour mettre en place des hiérarchies d'agents, le
des abstractions légères et rapides et un prototypage rapide de Julia est
étonnant - Déjà quelques frameworks ABM sont apparus.

Dans ABM, la notation par points est préférable pour exprimer les interactions d'agent:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Cette évidence n'est pas le cas d'utilisation le plus important, mais ce serait bien de le garder
sucre syntaxique pour réfléchir et coder sur les ABM.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95218555.

Dans ABM, la notation par points est préférable pour exprimer les interactions d'agent: Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Pourquoi est-ce mieux? Edit: Je veux dire dans ce contexte ABM spécifiquement.

S'il vous plaît, ne nous laissez pas entraîner dans des guerres de religion pour l'orthographe. @ dbeach24 , personne ne propose que a.b(c) soit équivalent en Julia à b(a,c) ; Ça ne va pas arriver.

Les points surchargés sont cruciaux pour l'interopérabilité naturelle avec d'autres langues. C'est une raison suffisante.

Subject.Verb (DirectObject)

Est assez naturel dans plusieurs contextes. De nombreux programmeurs OO sont habitués à
et bien qu'il ne s'agisse que d'une simple réorganisation de la fonction (A, B), cette réorganisation
fait beaucoup pour la lisibilité, l'OMI.
Le 22 avril 2015 à 10 h 32, "Andy Hayden" [email protected] a écrit:

Dans ABM, la notation par points est préférable pour exprimer les interactions d'agent:
Agent1.dosomething (Agent2) vs dosomething (Agent1, Agent2).

Pourquoi est-ce mieux?

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95256390.

Je le proposais. (Désolé, je ne voulais pas déclencher une guerre, je ne savais pas non plus
la suggestion serait impopulaire.) Je pensais avoir déjà vu cela arriver
dans les forums, mais ne s'est pas rendu compte qu'il avait déjà été rejeté comme un
mauvaise idée. Puis-je demander pourquoi? (Pouvez-vous m'indiquer un fil?)

Merci.
Le 22 avril 2015 à 11 h 09, "Steven G. Johnson" [email protected]
a écrit:

S'il vous plaît, ne nous laissez pas entraîner dans des guerres de religion pour l'orthographe. @ dbeach24
https://github.com/dbeach24 , personne ne propose que ab (c) soit
équivalent en Julia à b (a, c); Ça ne va pas arriver.

Les points surchargés sont cruciaux pour une interaction fluide avec d'autres langues.
C'est une raison suffisante.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/JuliaLang/julia/issues/1974#issuecomment -95266671.

Une des raisons de ne pas faire cela est que a.b recherche b dans la portée de a , tandis que b regarde seul b dans la portée englobante. Il serait très déroutant que l'accès en pointillé ne recherche parfois pas dans l'objet de gauche.

Btw, on considère que les fonctions de Julia ne sont pas recherchées à l'intérieur des objets mais dans la portée actuelle. Je pense que la crainte que les gens commencent à utiliser des fonctions recherchées à l'intérieur des objets est une des raisons qui empêchait la surcharge de points.

@toivoh , toute implémentation de la surcharge de points utiliserait la répartition de méthode existante, donc cela ne changerait pas le comportement de portée. @ dbeach24 , la raison fondamentale pour ne pas encourager l'utilisation aveugle de a.b(c) est que si vous avez trop de syntaxes pour faire la même chose, le langage et les bibliothèques se transforment en désordre. Il est préférable de choisir une orthographe et de s'y tenir, et la répartition multiple de Julia favorise b(a,c) car il est plus clair que b n'est pas "détenu" par a - le b(a,c) méthode a et c .

La plus grande exception, bien sûr, est pour appeler des bibliothèques externes dans des langages comme Python ou C ++, où il est agréable de pouvoir refléter la syntaxe de points de la bibliothèque que vous appelez. (Pour que, par exemple, les gens puissent traduire la documentation et les exemples directement dans Julia sans trop changer.)

Suis-je tout mouillé, mais ab (arg) ne voudrait-il pas dire prendre une fonction anonyme stockée dans le champ b de a, puis l'évaluer avec l'argument donné?

Envoyé de mon iPhone

Le 22 avril 2015, à 17 h 06, Steven G. Johnson [email protected] a écrit:

externe

@ScottPJones Cela fonctionne actuellement très bien, mais n'est généralement pas considéré comme un bon style.

Je n'étais pas préoccupé par le style ou pas, je pensais juste que puisqu'il avait déjà un sens, cela était cohérent avec la façon dont Julia fonctionne (c'est-à-dire pouvoir stocker des fonctions anonymes dans des champs), que c'était un bon argument _pas_ essayez de traiter ab (arg) comme s'il s'agissait de b (a, arg).
Je _pourrais_ avoir une utilité pour avoir un struct (type) avec des membres stockant des fonctions anonymes, où je charge des fonctions écrites en Julia à partir d'une base de données, puis je les analyse et stocke les fonctions dans un objet ...
Quel serait un meilleur style «Julian» pour faire quelque chose comme ça?

Merci!

@ScottPJones Je pense que cela ne devrait pas être équivalent *.

Il peut y avoir des exceptions à la règle "style", mais il doit y avoir un cas convaincant pour utiliser mettre une fonction dans le champ, comme pour la surcharge de points. Je pense que le problème est que les gens ne devraient pas faire ça bon gré mal gré / pour le plaisir / parce qu'ils le peuvent.

Cela pourrait être un exemple, mais il pourrait aussi y avoir une meilleure façon (ce n'est certainement pas la seule façon) ...

99% + du temps, il est préférable d'envoyer sur typeof (a); pas de champs de fonction, pas de surcharge de points.

* _Cependant, je pense que tout le monde sait à la seconde où cela arrivera, il y aura un paquet qui fera exactement cela ..._

En D, ils ont même un nom "syntaxe d'appel de fonction uniforme" pour a.b(arg) et c'est assez populaire, mais je pense que c'est assez profondément en contradiction avec la fonction générique, la manière dont Julia fonctionne. Si les fonctions en question sont anonymes ou complètement de type canard, alors je suppose que les choses fonctionneront, mais c'est assez restrictif IMO. Je ne pense pas qu'il y ait beaucoup de raison de stocker des champs de fonction dans un type composite, sauf par habitude des langages OO traditionnels basés sur les classes. Un meilleur endroit pour stocker des fonctions génériques si vous les chargez de quelque part serait dans les modules.

Mais nous sommes maintenant assez loin des «questions de fond». Je suis également en faveur d'être prudent avec la façon dont nous autorisons la surcharge de getfield, de ne pas autoriser la surcharge de getfield sur les modules, et de ne pas trop s'inquiéter de la syntaxe spéciale pour «true getfield».

@stevengj : Oui, et comme j'essayais de le dire, c'est l'une des raisons fondamentales pour lesquelles il n'arrivera jamais que a.b(c) devienne égal à b(a, c) , indépendamment de ce que nous faisons avec la surcharge de points autrement .

Il y a eu une discussion sur la liste de diffusion à ce sujet. De mon point de vue, l'article le plus pertinent (de @nalimilan) sur ce fil est: https://groups.google.com/d/msg/julia-users/yC-sw9ykZwM/-607E_FPtl0J

Ajoutant à l » @johnmyleswhite commentaire au sujet de la politique personnelle quand utiliser cette fonction - il me semble que certaines idées HTTP pourraient être utiles ici, et que getfield() ne devrait pas avoir des effets secondaires et setfield!() devrait être idempotent (c'est-à-dire que l'appeler plusieurs fois avec la même valeur devrait avoir le même effet que de l'appeler une fois). Pas nécessairement des règles strictes appliquées par le compilateur, mais des directives d'utilisation pour éviter que les choses ne deviennent trop folles.

J'ai publié une solution de contournement en utilisant des types paramétriques avec des paramètres de pointeur et convertis pour appeler un setter personnalisé lors de la définition d'un champ:
post: https://groups.google.com/forum/#!topic/julia -users / _I0VosEGa8o
code: https://github.com/barche/CppWrapper/blob/master/test/property.jl

Je me demande si je devrais utiliser cette approche dans mon package comme solution de contournement jusqu'à setfield! la surcharge est disponible, ou est-ce trop de contraintes sur le système de type paramétrique?

Je voudrais mentionner un avantage supplémentaire setfield! surcharge de getfield / setfield! , j'espère que c'est le bon endroit pour cela, je suis désolé sinon. (Un sujet connexe a été abordé sur https://groups.google.com/forum/#!topic/julia-users/ThQyCUgWb_Q)

Julia avec getfield / setfield! la surcharge permettrait une implémentation étonnamment élégante de la fonctionnalité de chargement automatique dans un _ paquet externe_. (Voir tout le travail acharné qui a dû être mis dans l'extension autoreload IPython https://ipython.org/ipython-doc/3/config/extensions/autoreload.html pour obtenir cette fonctionnalité.) L'idée du chargement automatique est que vous peut modifier les fonctions et les types dans les modules externes tout en travaillant avec le REPL.

TLDR: getfield / setfield! surcharge, dictionnaires et un package similaire à https://github.com/malmaud/Autoreload.jl devraient faire l'affaire.


Pour être plus précis, imaginez un package similaire à Autoreload.jl qui effectue les opérations suivantes.

Vous créez d'abord un module M.jl:

module M
type Foo
  field1::Int64
end
bar(x::Foo) = x.field1 + 1.0
end

Dans la REPL, vous tapez

julia> using Autoreload2
julia> arequire("M")
julia> foo = Foo(42)

Ensuite, vous changez M.jl en

module M
type Foo
  field1::Int64
  field2::Float64
end
bar(x::Foo) = x.field1+x.field2

Cela serait rechargé automatiquement et transformé en

# type redefinition removed as already done by Autoreload.jl
const field2_dict = Dict{UInt64,Float64}()
setfield!(x::Foo, ::Field{:field2}, value) = field2_dict[object_id(x)] = value
getfield(x::Foo, ::Field{:field2}) = field2_dict[object_id(x)]
<strong i="25">@do_not_inline</strong> bar(x::Foo) = x.field1 + x.field2

et puis dans le REPL vous pourriez faire

julia> foo.field2 = 3.14
julia> println(bar(foo)) # prints 45.14

Les performances ne seraient pas pires qu'avec Python, donc les personnes qui migrent leur flux de travail depuis le chargement automatique IPython ne perdraient rien en termes de performances. Et une fois que vous redémarrez le REPL, vous êtes de retour à la pleine performance.

J'en ai eu assez d'écrire a[:field][:field2][:morestuff](b[:random_stuff]) car ce n'est pas vraiment lisible. J'ai donc écrit cette petite macro qui fonctionne pour mes cas d'utilisation en 0.4 et 0.5
https://github.com/sneusse/DotOverload.jl

TL; DR
Une macro qui transforme l'AST d'une expression a.b -> getMember(a, :b)

Suppression de la version 0.6, car il n'y a pas de consensus sur le fait que c'est une bonne idée et il y a une proposition contradictoire sur ce qu'il faut faire avec la syntaxe dot.

@Keno : Avez-vous un lien avec la proposition en conflit?

Ne pensez pas que @StefanKarpinski l' a encore écrit, mais je m'attendrais à ce qu'il y ait bientôt un Julep à ce sujet.

J'ai trouvé object.fieldname plus agréable que les fonctions getter comme fieldname(object) ou get_fieldname(object) . Peut-être que object.fieldname (ou object$fieldname ) est un appel à getpublicfield (peut-être avec un meilleur nom) et object..fieldname étant le getfield (privé) pourrait être une bonne option. De cette façon, les types doivent définir getpublicfield au lieu de getters, et essayer de faire object.fieldname devrait donner un identifiant d'erreur le champ est privé (il sera privé s'il n'a pas de définition pour getpublicfield ).

J'ai ajouté l'étiquette de décision. Cette question a été longuement discutée et cela doit être fait ou non. Lors de la lecture de # 5848, il semblait que @JeffBezanson @StefanKarpinski et @stevengj le voulaient. Si oui, ce problème doit être marqué par un jalon afin qu'il ne soit pas oublié. Sinon, fermez. En tout cas, je pense que c'est un changement qui devrait être fait avant la version 1.0.

@JeffBezanson et Module (qui sera spécialement gérée); (iii) ne pas fournir de syntaxe spéciale pour Core.getfield (puisqu'il n'y a pas besoin pressant pour un getfield surchargé d'avoir le même nom qu'un champ "réel"; ce dernier peut simplement commencer par un trait de soulignement).

@stevengj : a.fieldname(b) doit également être prise en charge? Cela permettra de conclure à la discussion ci-dessus. De plus, il serait formidable de mettre une étiquette d'étape appropriée à cela (1.0?). Merci!

Jeff et moi n'avons pas discuté du cas multi-arguments. Mon sentiment est que nous pourrions aussi bien le soutenir, puisque vous pouvez le simuler de toute façon en renvoyant une fonction du cas no-arg (mais ce n'est pas critique de le faire tout de suite pour la même raison).

J'utilise un convertisseur pour convertir les valeurs et valider les données.
comme ça:

abstract AbstractAge{T}
abstract AbstractPerson
type PersonAge <: AbstractAge{AbstractPerson} 
    value::Int64
end

Base.convert(t::Type{AbstractAge{AbstractPerson}}, value::Int64) =  begin
  if value < 140 && value > 0
    PersonAge(value) 
  else
     throw(ErrorException("ValueError"))
  end
end

type Person <: AbstractPerson
  age::AbstractAge{AbstractPerson}
end 

a = Person(32)
a.age = 67

Voici une implémentation amusante en 3 lignes de ceci:

diff --git a/base/boot.jl b/base/boot.jl
index cd3ae8b..a58bb7e 100644
--- a/base/boot.jl
+++ b/base/boot.jl
@@ -266,6 +266,9 @@ Void() = nothing

 (::Type{Tuple{}})() = ()

+struct Field{name} end
+(::Field{f})(x) where {f} = getfield(x, f)
+
 struct VecElement{T}
     value::T
     VecElement{T}(value::T) where {T} = new(value) # disable converting constructor in Core
diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm
index b4cb4b5..59c9762 100644
--- a/src/julia-syntax.scm
+++ b/src/julia-syntax.scm
@@ -1685,7 +1685,7 @@
     (if (and (pair? e) (eq? (car e) '|.|))
         (let ((f (cadr e)) (x (caddr e)))
           (if (or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
-              `(call (core getfield) ,f ,x)
+              `(call (new (call (core apply_type) (core Field) ,x)) ,f)
               (make-fuse f (cdr x))))
         (if (and (pair? e) (eq? (car e) 'call) (dotop? (cadr e)))
             (make-fuse (undotop (cadr e)) (cddr e))

Je pense que a.b devrait en fait appeler une fonction de projection Field{:b}() au lieu de getfield , de sorte que vous obteniez déjà gratuitement des fonctions comme x->x.a . Cela permet également à getfield de toujours signifier un accès aux champs de bas niveau.

L'implémentation ci-dessus fonctionne complètement mais est assez difficile pour le compilateur (sysimg + 5%, ce qui est vraiment une agréable surprise). Cela nécessitera donc des heuristiques de spécialisation et certaines optimisations précoces doivent être mises à jour, mais nous espérons que cela sera viable.

Je suis surpris que les tests puissent passer avec cette implémentation. Cette sémantique ne rend-elle pas impossible pour codegen d'optimiser les références de modules? Il semble également que cela rendra les variables globales beaucoup plus chères (vitesse et mémoire). Il ne semble pas trop probable que cela apparaisse dans le sysimg. Bien que la dégradation de l'inférence semble avoir dû rendre le sysimg plus petit ici, donc 5% de pire ne semble pas être un bon début)

Oui, il y a du code dans jl_resolve_globals pour les convertir en GlobalRefs qui devraient être mis à jour. En dehors de cela, ceux-ci devraient être intégrés, il devrait donc être possible pour codegen de les gérer. En inférence, nous avons probablement juste besoin d'un cas spécial similaire à celui du tuple getindex .

nous avons probablement juste besoin d'un cas spécial similaire à celui du tuple getindex

Ce cas particulier espère et suppose qu'aucune méthode ne sera définie qui croise la signature de méthode intégrée getindex . Je ne vois pas comment ce cas est particulièrement applicable ici.

J'ajouterais une méthode pour ::Module et dirais que vous n'êtes pas autorisé à l'observer --- peut-être appliqué avec une erreur réelle.

@JeffBezanson Avez-vous une succursale avec ces trois lignes? J'ai essayé de les ajouter moi-même à une version locale, mais Julia semble avoir évolué maintenant et je n'ai pas pu le faire fonctionner.

Je dois dire que si je n'avais qu'un seul souhait pour une fonctionnalité dans julia 1.0, ce serait celle-ci. Cela résoudrait deux problèmes de longue date que j'ai à la fois dans Mimi et Query .

Le cas de Query, je pense, est assez générique. Je pense que l'on pourrait écrire une version de NamedTuples avec ceci qui n'aurait pas à générer de nouveaux types dans les macros, et cela me permettrait à son tour d'écrire des fonctions générées qui renvoient un NamedTuple avec un ensemble de champs qui sont calculé dans la fonction générée. Je pense que cela me permettrait d'écrire une version de type stable de blackrock / NamedTuples.jl # 4, qui est de loin ma plus grande pierre d'achoppement dans Query.jl en ce moment.

Pour faire court, ce serait super, super précieux pour toute l'histoire de la gestion des données en Julia.

La syntaxe ci-dessous est-elle disponible?

function (obj::MyType).plus(n::Int)
       return obj.val + n
end

Pourquoi diable voudriez-vous cette syntaxe?

Vouloir écrire obj.plus(n) au lieu de obj + n est un grave syndrome de Stockholm.

Ok, l'exemple donné était trivial (et mauvais).
C'était juste une idée d'utiliser cette syntaxe au lieu de surcharger getfield et de pouvoir utiliser des arguments.

Je suis surtout préoccupé par la fonctionnalité. Avoir une syntaxe abrégée pour la surcharge de getfield semble beaucoup moins important et ne vaut pas la peine de s'enliser. De plus, getfield et setfield! reflètent directement getindex et setindex! et sont donc quelque peu naturels chez Julia. Enfin, une utilisation intensive d'un style OO n'est pas vraiment un bon choix pour l'idiome de Julia, et je ne pense pas que nous voulons l'encourager par une syntaxe de définition de méthode de type OO (mais voir mon commentaire ci-dessus concernant les arguments).

Une chose qui m'est venue à l'esprit était que l'opérateur $ était obsolète. Maintenant, cela permet évidemment de faire quelque chose comme $(x, sym::Symbol) = ... déjà disponible, mais nous pourrions aussi envisager une réécriture syntaxique plus sophistiquée comme:

x$y          => $(x, ::Type{Val{:y}})
x$z(args...) => $(x, ::Type{Val{:z}}, args...)

Je pense que cela couvre déjà la plupart des cas mentionnés dans ce numéro sans surcharger getfield à part entière. Franchement, l'opérateur . est assez sémantiquement saturé dans Julia, donc quelque chose comme ça semble plus facile à digérer et est suffisamment pratique pour être toujours utile.

@quinnj , déjà proposé dans # 18696.

Puisque nous avons déjà . pour l'accès aux champs, cependant, il semble inélégant d'avoir deux opérateurs de type accès aux champs, l'un surchargeable et l'autre non. Et ce serait un peu artificiel pour les appels inter-langues à Python et à d'autres langages OO, qui utilisent presque universellement . .

Je ne vois pas l'interopérabilité avec d'autres langages comme un argument valable pour introduire quelque chose comme ça. C'est comme dire: "Le code Python ressemble à ceci, donc pour pouvoir prétendre être Python, nous devons le faire aussi." Je n'ai pas encore vu d'argument pour cela qui rendra Julia elle-même meilleure et / ou plus cohérente. Il est déjà bien établi que Julia ne fournit pas la syntaxe x.f() style POO; permettre des choses comme ça, c'est demander de l'incohérence.

@stevengj , une partie d'où je viens est le fait que x.f _n'est pas_ accès aux champs. Il n'y a pas de membre de champ réel f . Toute cette question concerne la surcharge de getfield et je pense que les principales préoccupations sont la confusion potentielle de savoir si x.f fait réellement référence à un champ ou fait autre chose sous le capot.

L'avantage de la réécriture syntaxique que j'ai proposée est qu'elle remplit l'histoire d'interopérabilité du langage sans introduire de confusion supplémentaire avec getfield; c'est ce à quoi je faisais référence lorsque j'ai mentionné que . serait sursaturé. Est-ce que x$f serait vraiment beaucoup plus encombrant? Je ne vois tout simplement pas pourquoi ce _has_ utilise l'opérateur point.

Toute cette question concerne la surcharge de getfield et je pense que les principales préoccupations sont la confusion potentielle de savoir si x.f fait réellement référence à un champ ou fait autre chose sous le capot.

Je ne pense pas qu'il y ait de risque de confusion dans la proposition de trois lignes de a.b est toujours abaissé dans un appel de fonction de projection, et jamais un appel getfield . Et puis il y a une méthode par défaut / fallback dans la base pour la fonction de projection qui appelle getfield . Les utilisateurs peuvent ajouter des méthodes pour leurs propres types à cette fonction de projection s'ils le souhaitent. getfield signifie toujours un accès aux champs de bas niveau dans cette proposition, et les utilisateurs n'ajouteraient pas de méthodes à getfield (je suppose que c'est ce que vous vouliez dire par "surcharge de getfield"). Cela me semble très clair. Cela dit, je ne sais pas si cette proposition est toujours sur la table ou non, il semble y avoir des préoccupations du compilateur que je ne comprends pas :)

J'adorerais vraiment avoir cette fonctionnalité (je l'ai toujours adoré dans Scala). IMHO accéder aux champs est directement très naturel dans Julia (comparé, par exemple, à la manière Java de définir défensivement les getters et les setters pour tout). Mais sans la possibilité d'ajouter des champs «virtuels», il devient très difficile de faire évoluer / refactoriser des structures sans casser beaucoup de code.

Je sais que cela a été étiqueté v2.0, mais j'aimerais revenir sur ce point dans l'espoir de reconsidérer cela pour la v1.0. Cette fonctionnalité sera très utile pour les développeurs de packages / bibliothèques.

Lors de la construction d'un package pour les utilisateurs, la surcharge de l'accesseur d'attribut permettra aux responsables du package de présenter une interface utilisateur beaucoup plus propre. Je dirais que,

complex_data_structure.attribute

est facile à taper que

get(complex_data_structure, :attribute)

get est une fonction requise pour obtenir des données décrites par :attribute .

Cela pourrait également améliorer la découvrabilité dans le REPL, et profiterait au code de langage et à l'exploration des données.

De plus, un intercepteur d'attributs de setter serait également extrêmement précieux. Prenons l'exemple suivant (déjà trouvé dans beaucoup de mes scripts actuellement :)),

set(complex_data_structure, :attribute, modify_attribute(get(complex_data_structure, :attribute), additional_arguments))

Si un getter et un setter d'attributs étaient inclus dans Julia, ce qui précède deviendrait le suivant.

complex_data_structure.attribute = modify_attribute(complex_data_structure.attribute, additional_arguments)

Cela, à mon humble avis, est beaucoup plus lisible. (Note de bas de page: je pourrais utiliser une composition de tuyaux pour la rendre plus lisible, mais le additional_arguments le compliquerait encore une fois. Et l'argument ici serait toujours valable.)

De plus, les valeurs contraignantes fournies par un utilisateur sont un cas d'utilisation très courant, et ce sera vraiment génial d'avoir dans la v1.0. Cela améliorera considérablement les interfaces utilisateur pour plusieurs packages. Actuellement, à ma connaissance, il ne semble pas possible de faire ce qui suit (quelqu'un peut-il me corriger si je me trompe)?

module point

mutable struct Point
    x::Int
    y::Int
    function Point(x, y)
        if x < 0 || y < 0
            throw(error("Only non-negative values allowed"))
        end
        this = new(x, y)
    end
end

end
# point

p1 = point.Point(-1, 0)
# Only non-negative values allowed

# Stacktrace:
# [1] point.Point(::Int64, ::Int64) at ./In[30]:8
# [2] include_string(::String, ::String) at ./loading.jl:515

p1 = point.Point(0, 0);
p1.x = -1;
p1
# point.Point(-1, 0)

Le constructeur peut restreindre les entrées de domaine à une structure immuable, mais un utilisateur peut toujours entrer des valeurs qui ne sont pas valides. Les getters et les setters d'attributs aideraient ici, car ils rendraient les commentaires sur la validité des données ici plus immédiats, ce qui rendrait l'interface beaucoup plus propre en tant qu'utilisateur de la langue.

Cela profitera également grandement à d'autres packages tels que PyCall.jl et DataFrames.jl pour créer des interfaces sans doute meilleures et plus intuitives pour les utilisateurs. Les auteurs de ces packages ont également exprimé leur intérêt ci-dessus à avoir cette fonctionnalité.

Pensées? En lisant le fil, il semble que ce soit déjà assez proche de la fin. Je suis curieux de savoir ce que pensent les auteurs à ce sujet?

Implémenté dans une macro facultative de type stable ici: https://github.com/bramtayl/DotOverloading.jl. Je pense que cela a du potentiel pour Base.

@bramtayl , je pense que le fait de devoir mettre @overload_dots avant les expressions a.b vainc le point ici.

Je ne suggérais pas l'ajout de la macro à la base; Je suggérais que la stratégie utilisée par la macro soit intégrée à l'analyseur. Cependant, c'est une macro qui peut être exécutée sur des fichiers entiers ou même piratée dans un IDE.

Cela impliquerait que a.b soit analysé en Expr(:., :a, :(Val{:b}()) et abaissé à get_field_overloadable(a, Val{:b}())

@bramtayl , voir l' implémentation de Jeff

Un problème avec la surcharge de getfield est que certaines bibliothèques n'ont pas d'interface raisonnable avec les composants internes d'une structure de données. Quand tout ce que je veux, c'est modifier un point de données dans une structure afin que je puisse exécuter mon code et faire mon rapport pour demain, je n'ai pas particulièrement hâte d'éditer du code dans une bibliothèque à trois niveaux profondément dans ma dépendance afin de pouvoir pour accéder directement au point de données d'une manière raisonnable et rapide. Je veux avoir l'impression de contrôler les structures de données que j'utilise.

Le deuxième point est que lorsque j'utilise getfield et setfield, je veux une attente raisonnable que j'accède directement à la structure de données au lieu d'un énorme mécanisme de fonction. De plus, le mot-clé struct est utilisé pour définir un type, vous rappelant C, et j'ai l'impression que cela vous donne l'espoir que l'accès à getfield devrait être direct.

Donc, je pense que l'utilisation de l'opérateur $ place est un compromis raisonnable car on ne s'attend pas à ce que l'accès soit direct comme dans getfield, c'est déjà un caractère spécial dans d'autres contextes et donc ce ne sera pas trop surprenant quand il est utilisé de cette manière, il sera facile de le rendre obsolète car peu de gens l'utilisent et parce que ce ne sera plus une fonction, et il est utilisé de la même manière dans R.

Un problème avec la surcharge de getfield est que certaines bibliothèques n'ont pas d'interface raisonnable avec les composants internes d'une structure de données.

L'implémentation de getfield (ou setfield ).

Je voudrais également plaider pour que cela devienne la version 1.0. Je me trouve souvent déchiré entre l'écriture de getters / paramètres pour garder mon code quelque peu flexible (et ensuite me demander comment les nommer), ou «autoriser / encourager» l'utilisateur de mon code à utiliser l'accès direct aux champs.

L'écriture défensive de beaucoup de getters et de setters à l'avant ne me semble pas très Julian - après tout, Julia n'a pas de champs privés / protégés et encourage donc l'utilisateur à accéder directement aux champs. Ensuite, il y a la question de savoir comment nommer un grand nombre de fonctions getter / setter sans risquer beaucoup de conflits avec d'autres paquets. Et l'alternative, l'ajout de méthodes pour getfield et setfield! (ou similaire), distribuées sur Val{:fieldname} ou plus, ne semble pas non plus très conviviale.

D'un autre côté, si l'accès direct aux champs verrouille fondamentalement toutes les structures pour toujours, il ne devrait clairement pas être encouragé - en particulier pour le code de longue durée. La mise en œuvre de @JeffBezanson semble être un moyen si

À droite, @davidanthoff. Je dois confondre surcharge de getfield et surcharge de la syntaxe d'accès au champ . . Je voulais dire une surcharge de la syntaxe d'accès aux champs.

Il existe des moyens rétrocompatibles pour ajouter cette fonctionnalité, donc elle pourrait être ajoutée dans une version 1.x, mais cela ne se produit pas dans la version 1.0. Nous n'avons pas de design fini ni de temps pour l'implémenter, même si nous en avions un.

Il serait extrêmement pratique d'utiliser la syntaxe à points sur les pointeurs vers les structures (C-).

Ma syntaxe préférée serait d'utiliser des points pour déplacer et convertir des pointeurs uniquement, et d'utiliser [] pour deref.

Donc, struct somepair a :: Int b :: Int end, et p :: Ptr {somepair}, alors pa est un pointeur vers le champ a, et j'écris dessus avec pa [] = 3, ou lit avec x = Pennsylvanie[].

Ensuite, nous avons juste besoin de déclarations de types en avant, et éventuellement d'un moyen d'importer les en-têtes C, puis envelopper C devient un jeu d'enfant.

Ps, pour clarifier, quelque chose comme:

function getfield(p::Ptr{T}, fn::Symbol) # dispatch on values of T and fieldname
ftype = fieldtype(T, fn)
offset = fieldoffset(T,fn)
return convert(Ptr{ftype}, p+offset)
end

getindex(p::Ptr{T}) where T = unsafe_load(p)
setindex!(p::Ptr{T}, v) where T = unsafe_store!(p, convert(T,v))

Pour les points bonus, les types de la bibliothèque d'exécution de julia pourraient être exportés, et pointer_from_objref pourrait en fait nous donner un pointeur bien typé pour une introspection plus pratique.

J'ai le sentiment que les syndicats devraient travailler un peu automatiquement, simplement en ayant deux champs avec le même décalage?

@chethega Je pense que vous recherchez https://github.com/JuliaLang/julia/pull/21912 , qui fournit à peu près la même fonctionnalité, sans les problèmes liés à l'adoption du modèle de mémoire de C (violations de type punning, out-of -bounds access, aliasing des pointeurs intérieurs, etc., qui limitent les optimisations de performances possibles dans cette langue).

La propagation constante rend-elle cela plus faisable?

Veuillez changer le jalon en 1.0! :)

@ Liso77 Que voulez-vous dire? Ceci est déjà implémenté sur git master, comme indiqué dans l'état lors de la fermeture.

@nalimilan désolé si je comprends mal! Mais je pensais que comme 1.x sont étiquetés des choses différées qui vont être résolues après 1.0. Et ceci est résolu maintenant ...

L'open source est une communauté décentralisée. Le jalon définit ce qui devrait être terminé d'ici la 1.0, mais les contributeurs et travaillent sur ce qu'ils veulent. Dans ce cas, quelqu'un voulait cela dans la version 1.0, alors ils ont contribué au code pour y parvenir.

@ Liso77 Si je comprends bien, ce ne sera pas dans la v1.0. La date de gel des fonctionnalités de Julia v1.0 était fixée au 15 décembre, mais ce problème a été clos le 17 décembre, donc je pense que nous pouvons nous y attendre dans une version 1.x. Les développeurs principaux peuvent me corriger si mon interprétation est incorrecte.

Non, il est fusionné dans master et sera dans la prochaine version.

:) Bien! J'ai juste pensé qu'il était bon d'étiqueter 1.0 ce qui sort dans 1.0. Si ce n'est pas voulu, alors désolé de déranger! :)

Je pense que le fichier NEWS est un meilleur moyen de voir ce qui se passe dans la version 1.0.

Le jalon a été ajouté pour signifier "peut être implémenté sans rompre la compatibilité dans la série de versions 1.x", mais puisque le code était prêt, il a quand même été fusionné avant le gel des fonctionnalités 1.0. J'ai supprimé le jalon pour plus de clarté, mais à ce stade, tout ce qui est fusionné dans master sera dans la version 1.0.

Merci pour la clarification! C'est excitant! Le dossier NEWS était également particulièrement éclairant.

Merci d'avoir supprimé! Je suis également très heureux qu'il arrive dans la version 1.0! :)

Je me demande s'il existe un moyen de supporter ces nouveaux "champs définis dynamiquement" en auto-complétion, par exemple en permettant de surcharger fieldnames ?. Cela pourrait être très puissant, pour une utilisation interactive, par exemple lorsqu'il s'agit de DataFrame s (en supposant qu'ils supporteront df.column_name à l'avenir) avec de nombreuses colonnes et / ou de longs noms de colonnes.

Je suppose qu'en ce moment le REPL (tab-expansion), IJulia, etc. regarde la définition de type, pas l'instance? Mais peut-être que cela pourrait être changé, pour une utilisation interactive. Il est probablement impossible de prendre en charge dans un IDE comme Juno, car les instances ne sont pas disponibles pendant le codage.

@oschulz , vous pouvez déjà surcharger fieldnames :


julia> struct Foo; foo; end

julia> fieldnames(Foo)
1-element Array{Symbol,1}:
 :foo

julia> Base.fieldnames(::Type{Foo}) = [:bar, :baz]

julia> fieldnames(Foo)
2-element Array{Symbol,1}:
 :bar
 :baz

Et il semble que fieldnames est ce que la REPL regarde:

julia> x = Foo(3)
Foo(3)

julia> x.ba<tab>
bar baz

@yurivish n'est-ce pas - mais est-il "sûr" de le faire actuellement? Je ne sais pas quoi d'autre repose sur fieldnames .

Si ce n'est pas sûr, il devrait être possible de définir un complete_fieldnames(x) = fieldnames(x) , d'utiliser complete_fieldnames pour les complétions REPL et de surcharger cela pour les complétions personnalisées.

Oui, c'est pourquoi j'en parlais - au cas où quelque chose devrait être changé / ajouté dans Base, afin que REPL et ses amis puissent en profiter plus tard. Compte tenu du gel des fonctionnalités 0.7 ...

C'est pourquoi je suis content que nous ayons appelé la fonction getproperty ; cela aide à clarifier qu'il ne fait pas référence à la même chose que fieldnames . Interférer avec fieldnames pourrait certainement causer de sérieux problèmes.

Nous pourrions avoir besoin d'un propertynames pour donner les valeurs de propriété valides. Bien sûr, ce n'est peut-être pas un concept bien défini, alors peut-être que nous voulons quelque chose de plus spécifique à l'achèvement.

J'avais le sentiment que cela nécessiterait peut-être une analyse plus approfondie et la contribution des experts (merci!) - devrions-nous ouvrir un nouveau numéro pour cela?

Cette page vous a été utile?
0 / 5 - 0 notes