Julia: Examen de la cohérence des API

Créé le 2 févr. 2017  ·  131Commentaires  ·  Source: JuliaLang/julia

Je commence cela comme un endroit pour laisser des notes sur les choses à prendre en compte lors de la vérification de la cohérence de l'API dans Julia 1.0.

  • [x] Hiérarchisation de la Convention. Liste et hiérarchisation de nos conventions `` ce qui vient en premier '' en termes d'arguments de fonction pour do-blocks, d'arguments IO pour les fonctions qui impriment, de sorties pour les fonctions sur place, etc. (https://github.com/JuliaLang/julia/issues/ 19150).

  • [] Arguments positionnels et mots clés. Il y a longtemps, nous n'avions pas d'arguments de mots-clés. Ils sont encore parfois évités pour des raisons de performances. Nous devons faire ce choix en fonction de ce qui constitue la meilleure API, et non sur ce type de bagage historique (les problèmes de performance des mots clés doivent également être résolus afin que ce ne soit plus une considération).

  • [] Outils de métaprogrammation. Nous avons beaucoup d'outils comme @code_xxx qui sont associés à des fonctions sous-jacentes comme code_xxx . Ceux-ci doivent se comporter de manière cohérente: des signatures similaires, s'il existe des fonctions avec des signatures similaires, assurez-vous qu'elles ont des versions de macro similaires. Idéalement, ils devraient tous renvoyer des valeurs, plutôt que certaines valeurs de retour et d'autres résultats d'impression, bien que cela puisse être difficile pour des choses comme le code LLVM et le code d'assemblage.

  • [] Équivalence de nom de fichier IO <=>. Nous autorisons généralement la transmission des noms de fichiers sous forme de chaînes à la place des objets IO et le comportement standard consiste à ouvrir le fichier dans le mode approprié, à transmettre l'objet IO résultant à la même fonction avec les mêmes arguments, puis à garantir que l'objet IO est fermé par la suite. Vérifiez que toutes les fonctions acceptant les E / S appropriées suivent ce modèle.

  • [] API Reducers. Assurez-vous que les réducteurs ont des comportements cohérents - tous prennent une fonction de carte avant la réduction; arguments de dimension congruente, etc.

  • [] Arguments de dimension. Un traitement cohérent des arguments d'entrée "calculer à travers cette [ces] dimension (s]", quels types sont autorisés, etc., examinez si les faire en tant que mots-clés args pourrait être souhaité.

  • [] Paires mutantes / non mutantes. Vérifiez que les fonctions non mutantes sont associées à des fonctions mutantes là où cela a du sens et vice versa.

  • [] Tuple contre vararg. Vérifiez qu'il existe une cohérence générale entre le fait que les fonctions prennent un tuple comme dernier argument ou un vararg.

  • [] Unions vs nullables vs erreurs. Règles cohérentes sur le moment où les fonctions doivent lancer des erreurs, et quand elles doivent renvoyer des Nullables ou des Unions (par exemple, parse / tryparse, match, etc.).

  • [] Soutenez les générateurs aussi largement que possible. Assurez-vous que toute fonction qui pourrait raisonnablement fonctionner avec des générateurs le fait. Nous sommes déjà assez bons à ce sujet, mais je suppose que nous en avons oublié quelques-uns.

  • [] Sélection du type de sortie. Soyez cohérent quant à savoir si les API de "type de sortie" doivent être en termes de type d'élément ou de type de conteneur global (réf. # 11557 et # 16740).

  • [x] Choisissez un nom. Il existe quelques fonctions / opérateurs avec des alias. Je pense que c'est bien dans les cas où l'un des noms est non-ASCII et la version ASCII est fournie afin que les gens puissent toujours écrire du code pur-ASCII, mais il y a aussi des cas comme <: qui est un alias pour issubtype où les deux noms sont ASCII. Nous devrions en choisir un et désapprouver l'autre. Nous avons déconseillé is en faveur de === et devrions faire de même ici.

  • [] Cohérence avec DataStructures . Cela dépasse quelque peu la portée de Base Julia, mais nous devons nous assurer que toutes les collections de DataStructures ont des API cohérentes avec celles fournies par Base. La connexion dans l'autre sens est que certains de ces types peuvent expliquer comment nous finissons par concevoir les API dans Base, car nous voulons qu'elles s'étendent de manière fluide et cohérente.

  • [] NaNs contre DomainErrors. Voir https://github.com/JuliaLang/julia/issues/5234 - ayez une politique pour savoir quand faire quoi et assurez-vous qu'elle est suivie de manière cohérente.

  • [] Générateur de collection <=>. Parfois, vous voulez une collection, parfois vous voulez un générateur. Nous devrions parcourir toutes nos API et nous assurer qu'il existe une option pour les deux là où cela a du sens. Il était une fois une convention pour utiliser un nom en majuscule pour la version du générateur et un nom en minuscule pour la version désireuse et renvoyant une nouvelle collection. Mais personne n'a jamais prêté attention à cela, alors peut-être avons-nous besoin d'une nouvelle convention.

  • [] Fonctions d'ordre supérieur sur les associations. Actuellement, certaines fonctions d'ordre supérieur parcourent les collections associatives avec la signature (k,v) - par exemple map , filter . D'autres itèrent sur des paires, c'est-à-dire avec la signature kv , obligeant le corps à déstructurer explicitement la paire en k et v - par exemple all , any . Cela devrait être revu et mis en cohérence.

  • [x] Convertir contre construire. Autorisez la conversion le cas échéant. Par exemple, il y a eu plusieurs problèmes / questions sur convert(String, 'x') . En général, la conversion est appropriée lorsqu'il y a une seule transformation canonique. La conversion de chaînes en nombres en général n'est pas appropriée car il existe de nombreuses façons textuelles de représenter les nombres, nous devons donc analyser à la place, avec des options. Cependant, il existe un moyen canonique unique de représenter les numéros de version sous forme de chaînes, nous pouvons donc les convertir. Nous devons appliquer cette logique soigneusement et universellement.

  • [] Vérifiez l'exhaustivité de l'API des collections. Nous devrions examiner les fonctions de bibliothèque standard pour les collections fournies par d'autres langages et nous assurer que nous avons un moyen d'exprimer les opérations communes qu'ils ont. Par exemple, nous n'avons pas de fonction flatten ou de fonction concat . Nous devrions probablement.

  • [] Audit de soulignement.

deprecation

Commentaire le plus utile

Audit de soulignement

Voici une analyse de tous les symboles exportés à partir de Base qui contiennent des traits de soulignement, ne sont pas obsolètes et ne sont pas des macros de chaîne. La principale chose à noter ici est que ce sont uniquement des noms exportés; cela n'inclut pas les noms non exportés que nous disons aux gens d'appeler qualifiés.

J'ai séparé les choses par catégorie. Espérons que ce soit plus utile que gênant.

Réflexion

Nous avons les macros suivantes avec les fonctions correspondantes:

  • [] @code_llvm , code_llvm
  • [] @code_lowered , code_lowered
  • [] @code_native , code_native
  • [] @code_typed , code_typed
  • [] @code_warntype , code_warntype

Quelle que soit la modification appliquée aux macros, le cas échéant, elle doit être appliquée de la même manière aux fonctions.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, voir # 25436 pour une précédente tentative de changement de nom)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref pourrait peut-être faire avec un nom plus descriptif, peut-être quelque chose comme address ?

Alias ​​pour l'interopérabilité C

Les alias de type contenant des traits de soulignement sont C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t et Cwchar_t . Ceux qui se terminent par _t devraient rester, car ils sont nommés pour être cohérents avec leurs types C correspondants.

C_NULL est l'étrange ici, étant le seul alias C contenant un trait de soulignement qui n'est pas reflété en C (puisque dans C c'est juste NULL ). Nous pourrions envisager d'appeler cela CNULL .

  • [] C_NULL

Comptage de bits

  • [] count_ones
  • [] count_zeros
  • [] trailing_ones
  • [] trailing_zeros
  • [] leading_ones
  • [] leading_zeros

Pour une discussion sur la façon de les renommer, voir # 23531. Je suis très favorable à la suppression des traits de soulignement pour ceux-ci, ainsi que de certains des remplacements proposés dans ce PR. Je pense que cela devrait être reconsidéré.

Opérations dangereuses

  • [] unsafe_copyto!
  • [] unsafe_load
  • [] unsafe_pointer_to_objref
  • [] unsafe_read
  • [] unsafe_store!
  • [] unsafe_string
  • [] unsafe_trunc
  • [] unsafe_wrap
  • [] unsafe_write

Il est probablement acceptable de les conserver tels quels; la laideur du trait de soulignement souligne encore leur insécurité.

Indexage

  • [] broadcast_getindex
  • [] broadcast_setindex!
  • [] to_indices

Apparemment, broadcast_getindex et broadcast_setindex! existent. Je ne comprends pas ce qu'ils font. Peut-être pourraient-ils utiliser un nom plus descriptif?

Fait intéressant, la version d'index unique de to_indices , Base.to_index , n'est pas exportée.

Traces

  • [] catch_backtrace
  • [x] catch_stacktrace -> stacktrace(catch_backtrace()) (# 25615)

Ce sont probablement les équivalents de bloc catch de backtrace et stacktrace , respectivement.

Tâches, processus et signaux

  • [] current_task
  • [] task_local_storage
  • [] disable_sigint
  • [] reenable_sigint
  • [] process_exited
  • [] process_running

Ruisseaux

  • [] redirect_stderr
  • [] redirect_stdin
  • [] redirect_stdout
  • [x] nb_available -> bytesavailable (# 25634)

Ce serait bien d'avoir une fonction de redirection IO -> IO plus générale dans laquelle tout cela pourrait être combiné, par exemple redirect(STDOUT, io) , supprimant ainsi à la fois les traits de soulignement et les exportations.

Promotion

  • [] promote_rule
  • [] promote_shape
  • [] promote_type

Voir # 23999 pour une discussion pertinente concernant promote_rule .

Impression

  • [x] print_with_color -> printstyled (voir # 25522)
  • [] print_shortest (voir # 25745)
  • [] escape_string (voir # 25620)
  • [] unescape_string

escape_string et unescape_string sont un peu bizarres en ce sens qu'ils peuvent imprimer dans un flux ou renvoyer une chaîne. Voir # 25620 pour une proposition de déplacer / renommer ces derniers.

Chargement du code

  • [] include_dependency
  • [] include_string

include_dependency . Est-ce même utilisé en dehors de Base? Je ne peux pas penser à une situation où vous voudriez cela au lieu de include dans un scénario typique.

include_string . N'est-ce pas juste une version officiellement sanctionnée de eval(parse()) ?

Les choses que je n'ai pas pris la peine de catégoriser

  • [x] gc_enable -> GC.enable (# 25616)
  • [] get_zero_subnormals
  • [] set_zero_subnormals
  • [] time_ns

get_zero_subnormals et set_zero_subnormals pourraient faire avec des noms plus descriptifs. Doivent-ils être exportés?

Tous les 131 commentaires

Toutes mes excuses si ce n'est pas le bon endroit pour le mentionner, mais ce serait bien d'être plus cohérent avec les traits de soulignement dans les noms de fonctions à l'avenir.

Non, c'est un bon endroit pour ça. Et oui, nous devrions nous efforcer d'éliminer tous les noms où des traits de soulignement sont nécessaires :)

  • traitement cohérent des arguments d'entrée "calculer sur cette [ces] dimension (s]", quels types sont autorisés, etc., déterminer si les utiliser en tant que mots-clés args pourrait être souhaité
  • listant et priorisant nos conventions de premier ordre en termes d'arguments de fonction pour do-blocks, d'arguments IO pour les fonctions qui impriment, de sorties pour les fonctions in-situ, etc.

Pour le deuxième point de @tkelman , voir https://github.com/JuliaLang/julia/issues/19150

Il y avait aussi un récent Julep concernant l'API pour find et les fonctions associées: https://github.com/JuliaLang/Juleps/blob/master/Find.md

Devrions-nous abandonner put! et take! sur les canaux (et peut-être faire de même pour les contrats à terme) puisque nous avons push! et shift! sur eux? Suggérant simplement de supprimer 2 mots redondants dans l'API.

Je soupçonne que shift! est convivial. Un candidat est fetch! nous avons déjà fetch qui est la version non mutante de take!

réf # 13538 # 12469

@amitmurthy @malmaud

Edit: Il serait même judicieux de réutiliser send et recv sur les chaînes. (Je suis surpris que ceux-ci ne soient utilisés que pour les UDPSockets pour le moment)

+1 pour remplacer put! / take! par push! / fetch!

J'ajouterai le changement @inferred nom @test_inferred .

Vérifiez que les spécialisations sont cohérentes avec les fonctions les plus génériques, c'est-à-dire pas quelque chose comme # 20233.

Passez en revue toutes les fonctions exportées pour vérifier si elles peuvent être éliminées en les remplaçant par des envois multiples, par exemple print_with_color

L'appariement typique est push! et shift! lorsque vous travaillez avec une structure de données de type file d'attente.

Si nous n'utilisons pas l'appariement de noms typique pour ce type de structure de données parce que nous craignons que l'opération entraîne une surcharge de communication qui ne soit pas correctement transmise par ces noms, alors je ne pense pas que push! sens non plus. send et recv pourraient vraiment être mieux.

Vérifiez peut-être qu'il existe une cohérence générale entre le fait que les fonctions prennent un tuple comme dernier argument ou un vararg.

Peut-être trop gros pour ce problème, mais il serait bon d'avoir des règles cohérentes sur le moment où les fonctions doivent renvoyer des erreurs et quand elles doivent renvoyer Nullable s ou Union s (par exemple parse / tryparse , match , etc.)

Aucun problème trop gros, @simonbyrne - c'est la liste de lessive.

Btw: ce n'est pas vraiment pour des changements spécifiques (par exemple, renommer des fonctions spécifiques) - c'est plus sur des types de choses que nous pouvons examiner. Pour les modifications proposées spécifiques, ouvrez simplement un numéro proposant ce changement.

Nous avons beaucoup d'outils comme @code_xxx qui sont associés à des fonctions sous-jacentes comme code_xxx

Je ne sais pas si c'est ce dont vous parlez, mais consultez CreateMacrosFrom.jl

  • Si les API de "type de sortie" doivent être en termes de type d'élément ou de type de conteneur global (réf # 11557 et # 16740)
  • Documenter toutes les fonctions exportées (y compris les doctests)

Documenter toutes les fonctions exportées (y compris les doctests)

si cela en fait partie, alors peut-être aussi: n'oubliez pas d'étiqueter vos tests avec le numéro de problème / pr. Il est beaucoup plus facile de comprendre pourquoi ce test existe. Je sais comment fonctionne git blame, mais lors de l'ajout de tests (juste pour donner un exemple), ce qui est testé est parfois un peu mystérieux, et ce serait génial si le numéro de problème / pr était toujours là.

@dpsanders : et les macros exportées! par exemple @fastmath n'a pas de docstring.

C'est très mineur, mais les fonctions string et Symbol font presque la même chose et ont une capitalisation différente. Je pense que symbol aurait plus de sens.

@amellnik La différence est que Symbol est un constructeur de type et string est une fonction régulière. IIRC nous avions l'habitude d'avoir symbol mais il était obsolète au profit du constructeur de type. Je ne suis pas convaincu qu'un changement soit nécessaire pour cela, mais je pense que nous devrions utiliser le constructeur String à la place de string .

si quoi que ce soit, je pense que nous devrions utiliser le constructeur String à la place de string.

Non, ce sont des fonctions différentes et ne devraient pas être fusionnées

julia> String(UInt8[])
""

julia> string(UInt8[])
"UInt8[]"

Non, ce sont des fonctions différentes et ne devraient pas être fusionnées

Cela ressemble à une situation où string(args...) devrait simplement être obsolète en faveur de sprint(print, args...) , alors - avoir à la fois string et String est déroutant. Nous pourrions nous spécialiser sur sprint(::typeof(print), args...) pour récupérer toute performance perdue. Dans ce sens, il peut également être judicieux de rendre obsolète repr(x) pour sprint(showall, args...) .

Cela semble correct, même si appeler string pour transformer quelque chose en une chaîne semble assez standard ....

appeler une chaîne pour transformer quelque chose en une chaîne semble assez standard

Oui, mais c'est là que la déconnexion entre String et string entre en jeu.

sprint(print, ...) se sent redondant. Si on se débarrasse de string , on peut renommer sprint en string donc on obtient string(print, foo) et string(showall, foo) qui se lit bien à mon avis .

Cela peut être un cas où la cohérence est surfaite. Je pense que c'est bien d'avoir string(x) pour "donnez-moi simplement une représentation sous forme de chaîne de x". Si cela va être plus compliqué que cela, par exemple vous demander de spécifier la fonction d'impression à utiliser, alors utiliser un autre nom comme sprint sens.

Ce serait aussi bien avec moi de renommer String(UInt8[]) en autre chose, et d'utiliser String au lieu de string . string nous donne un peu plus de flexibilité à l'avenir pour changer le type de chaîne que nous retournons, mais cela ne semble pas susceptible de se produire.

Est-ce que reinterpret(String, ::Vector{UInt8} sens, ou est-ce un jeu de mots sur reinterpret ?

Cela semble logique.

Un problème est que cette fonction copie parfois, de sorte que ce nom est quelque peu trompeur.

C'est vrai, mais les chaînes sont censées être immuables, donc nous pouvons probablement nous en sortir.

Il existe également une méthode String(::IOBuffer) , mais il semble que cela pourrait être obsolète à readstring .

J'ai également pensé à votre proposition de modification d'API, mais l'interface de string(a, b...) est qu'elle stringifie et concatène ses arguments, ce qui créerait une exception gotcha ennuyeuse pour les premiers arguments appelables. Si nous supprimons la concaténation de string cela pourrait fonctionner.

Oui, d'accord; la cohérence et éviter les pièges est le plus important.

Notant les problèmes # 18326 et # 3893 dans la catégorie "arguments de dimension".

Si je peux clouer sur un autre élément: s'assurer que le comportement des conteneurs de mutables est à la fois documenté et cohérent.

@ JaredCrean2 : pouvez-vous expliquer ce que vous entendez par là?

J'espère certainement que cela n'implique pas de faire beaucoup de "copies défensives".

Par exemple, si j'ai un tableau de types mutables et que j'appelle sort dessus, le tableau retourné pointe-t-il vers les mêmes objets que le tableau d'entrée, ou copie-t-il les objets et fait pointer le tableau retourné vers leur?

Les mêmes objets. Je suis presque sûr que toutes nos méthodes de tri de collection, getindex, filtrage, recherche, etc. suivent cette règle, non?

Je ne pense pas qu'il y ait un manque de clarté ou de cohérence sur ce point - ce sont toujours les mêmes objets.

En fait, je pense que la seule fonction standard où ce n'est pas le cas est deepcopy où tout l'intérêt est que vous obtenez tous les nouveaux objets.

Est-ce documenté quelque part?

Non - nous pourrions le faire, mais je ne sais pas où il serait préférable de le documenter. Pourquoi les fonctions feraient-elles des copies inutilement? Où avez-vous eu l'impression qu'ils pourraient le faire?

Bonjour. Je n'ai vu, je crois, aucune remarque sur la sérialisation des données.

Bientôt ou plus tard, les programmes Julia seront écrits et exécutés publiquement, les données commenceront à se stratifier parfois, pendant des années. Sérialisation des données, par exemple. la chaîne: objet aux octets pilotés par type (peut-être sur json ou ...) doit être construite pour être résistante au temps. Penser au versionnage sémantique et à l'API Web peut également compter.

Pouvons-nous nous attendre à ce que la sérialisation des données utilisateur reste proche de https://github.com/JuliaLang/julia/blob/v0.5.1/base/serialize.jl ?

Pourquoi les fonctions feraient-elles des copies inutilement? Où avez-vous eu l'impression qu'ils pourraient le faire?

Je ne sais pas s'ils le font ou non. Pour autant que je sache, le comportement n'est pas défini. D' après le commentaire de

Vous semblez impliquer une sorte de principe de moindre action, mais selon les détails de l'algorithme, ce qu'est la «moindre action» devient ambiguë. Afin d'obtenir une cohérence à travers l'API, je pense que des conseils plus spécifiques sont nécessaires.

@ o314 : il s'agit d'un problème de vérification de la cohérence des API, je ne suis pas sûr du

@ JaredCrean2 : que l'objet de premier niveau soit copié ou non doit certainement être documenté. Ce que je dis, c'est que les objets plus profonds ne sont jamais copiés, sauf par deepcopy (évidemment).

Ce que je dis, c'est que les objets plus profonds ne sont jamais copiés, sauf par deepcopy (évidemment).

Il y a eu une discussion récente à ce sujet dans le contexte de copy pour certains des wrappers de tableau, par exemple SubArray et SparseMatrixCSC mais aussi Symmetric , LowerTriangular . Il me semble que selon la politique mentionnée ci-dessus, copy serait un noop pour de tels types de wrapper. La politique que vous mentionnez est-elle le bon niveau d'abstraction ici? Par exemple, je pense que cela implique que si Array s étaient implémentés dans Julia (enveloppant un tampon), le comportement de copy sur Array s devrait alors changer en noop.

Si la convention est que les objets plus profonds ne sont jamais copiés, il ne reste plus qu'à les documenter. La documentation est une partie vraiment importante d'une API. Ce comportement peut vous sembler évident (peut-être parce que vous avez écrit des parties du code), mais d'un point de vue extérieur, ce n'est pas si évident.

Edit: n'a pas vu le post d'Andreas. C'est une considération intéressante.

@StefanKarpinski Je suis d'accord avec votre point de vue.
Et tous les principaux sujets invoqués ici sont très bons et intelligents.

Mais j'ai parfois un peu peur concernant l'équilibre entre processus et données chez Julia:

On peut appeler fortran ou c facilement à coup sûr,
Mais le code sera-t-il déployé aussi facilement sur les datacenters modernes, par exemple. aws lambda avec sa fonction de modèle de service. le code sera-t-il facilement appelable via Internet, ouvrir l'API?

Parfois, il faut réduire la charge fonctionnelle à l'échelle (pas de générique, pas de valeurs sur la signature de la fonction, pas de haut niveau sur les api publiques) et lier plus systématiquement les données derrière (schéma json / openapi).

J'ai vu une très bonne bibliothèque python couler de cette façon et c'est dommage.

Je pense que c'est un point crucial pour un langage 1.0 de garder les données et les fonctions équilibrées et modulaires pour pouvoir se déployer facilement sur le Web. Et pour cette fonction, l'interface doit être moins axée sur les animaux de compagnie et plus orientée vers le bétail si nécessaire.

Ce n'est peut-être pas le but de ce sujet.

@StefanKarpinski J'ai peut-être mal compris votre message. Quand tu as dit

si l'objet de niveau supérieur est copié ou non doit certainement être documenté

que signifie «objet de premier niveau»? Si j'ai x::Vector{MyMutableType} , est-ce que l'objet de niveau supérieur est x ou les éléments de x ?

L'objet de niveau supérieur fait référence à x lui-même, pas aux éléments de x .

@andreasnoack La notion d'objet de niveau supérieur doit faire référence à la structure abstraite implémentée, pas aux détails d'implémentation.

peut-être ajouter float et d'autres fonctions similaires qui agissent à la fois sur les types et les valeurs?

En passant en revue les notes de version 0.6, il semble étrange que iszero(A::Array{T}) soit introduit, alors que de nombreuses autres fonctions (par exemple sumabs , isinteger , isnumber ) sur les tableaux sont obsolète au profit de all(f,A) .

Il n'y a aucun tableau et ce sont des éléments nuls dans leur espace vectoriel. iszero teste de manière générique si quelque chose est l'inverse additif, ce que sont les tableaux nuls.

Ré. cohérence des traits de soulignement dans les noms de fonctions, voici un fil d'Ariane vers count_ones et count_zeros .

Il me semble que si vous voulez garder l'API Julia cohérente, vous aurez besoin d'un logiciel qui vous permet (a) de spécifier quelles sont les règles / conventions de l'API, (b) d'effectuer une analyse statique du code Julia pour détecter les écarts par rapport à ces règles / conventions, et (c) offrir des suggestions. Un tel outil profiterait à la fois à Julia Base et à tous les packages Julia. Un nouveau package Julia peut faire l'affaire. (La langue dans la joue: la première chose que ce paquet devrait faire est de suggérer son propre nom; APICheck.jl, ApiCheck.jl, API_Check.jl, APIChecker.jl, JuliaAPIChecker.jl, etc.) alors ne veux pas prendre les devants sur une telle chose. Cependant, cela ne me dérangerait pas de contribuer. Avez-vous des suggestions pour y parvenir?

Nous serions ravis d'avoir cela dans Lint.jl!

num2hex et hex2num (# 22031 et # 22088)

Je crois moi aussi que Lint.jl est le bon package pour la vérification de la cohérence de l'API Julia. Mais si nous suivons cette voie, la liste originale de Stefan doit être beaucoup plus précise. Par exemple, lorsqu'il écrit "nous devons nous assurer que toutes les collections de DataStructures ont des API cohérentes", les questions qui viennent à l'esprit sont:

  • Qu'est-ce qui fait d'une structure de données une collection? (Liste des collections en base)
  • Quelles sont les fonctions qu'une collection DOIT prendre en charge? (Un tableur de fonctions par collections)
  • Quelle devrait être la signature de chacune de ces fonctions?
  • Les API fournies par Base pour les collections sont-elles déjà cohérentes?

Pour gérer ces inventaires et analyses, nous pourrions vouloir ajouter un projet au référentiel Julia (projet n ° 8), ou au référentiel JuliaPraxis (suggéré hors ligne par TotalVerb) ou au référentiel Lint. Dans ce cas, nous aurions besoin de déterminer qui posséderait un tel projet, quelles personnes devraient être impliquées dès le début et qui devrait prendre les décisions finales sur ce que sont réellement les conventions de Julia (dans le but de linting).

Mais avant de progresser davantage dans ce sens, j'aime demander à @StefanKarpinski : quelles sont vos idées sur le travail de votre liste de problèmes de cohérence API Julia?

Je conviens que le préciser spécifiquement est une bonne idée. Déterminer ce que devrait être cette liste fait partie du travail ici - si vous souhaitez vous y attaquer, ce serait formidable.

Avons-nous vraiment besoin d'un Base.datatype_module et d'un Base.function_module?

Un "module" de fonction unifié (peut-être getmodule) distribuant sur le type de données et la fonction me semble plus cohérent.

Qu'en est-il des traits de soulignement dans @code_typed et amis?

C'est une belle opportunité de refactoring (la raison invoquée pour l'interdiction des soulignés). Vous pourriez avoir une macro @code avec le premier argument étant le type de code souhaité.

( @bramtayl , n'oubliez pas de mettre des contre-indications autour des macros car cela envoie un ping au "code" de l'utilisateur github sinon; @code )

FWIW, la complétion par tabulation ne fonctionne qu'avec les noms de fonction. Être capable de faire @code_<TAB> c'est bien ....

Si le refactoring est envisagé mais rejeté, alors la présence ou non de traits de soulignement est sans objet, car le seul point de l'interdiction des traits de soulignement est d'encourager le refactoring. En fait, dans ce cas, il semble que les traits de soulignement devraient être encouragés pour rendre le langage plus clair

Soulignez l'audit.

contre-proposition: nous auditons toujours ces noms, mais nous ajoutons à la place plus de traits de soulignement où cela rendrait le code plus facile à lire (codetyped vs code_typed, isos2 vs is_os2).

Je ne suis pas absolutiste à ce sujet. Je pense que code_typed est bien, utilement tab-complet comme le souligne @KristofferC , et il n'y a vraiment rien d'évident à passer comme argument pour sélectionner la sortie que vous voulez.

Pour moi, il semble plutôt que le navire a navigué en ajoutant plus de traits de soulignement, car nous aurions à déprécier essentiellement la moitié de la base. Par exemple, il y a 74 fonctions de prédicat qui commencent par is et seulement 6 qui commencent par is_ . Qu'est-ce qui a plus de sens, déprécier 6 ou 74?

Ok, il y a plusieurs objectifs contradictoires ici:

1) Rendre les noms plus lisibles
2) Réduire le taux de désabonnement du code
3) Encourager le refactoring

L'élimination des traits de soulignement par la collision des mots échoue sur les 3 fronts.

Que les méthodes show acceptant un flux ne soient pas ! semble incompatible avec la convention habituelle? Réf. https://github.com/JuliaLang/julia/pull/22604/commits/db9d70a279763ded5088016d9c3d4439a49e3fca#r125115063. Meilleur! (Modifier: je suppose que cela correspond aux méthodes write acceptant un flux.)

Il y a des incohérences avec l'API des traits. Certains traits sont calculés en appelant le trait comme
TypeArithmetic(Float64)
tandis que d'autres, cette fonction doit être écrite en minuscules:
iteratorsize(Vector{Float64})

Pensez à renommer size -> shape (xref # 22665)

Array{T,1}() devrait également être obsolète:

julia> Array{Int,1}()                                                                                                                  
0-element Array{Int64,1}                                                                                                               

julia> Array{Int,2}()                                                                                                                  
WARNING: Matrix{T}() is deprecated, use Matrix{T}(0, 0) instead.                                                                       

J'ai pensé aux collections. Nous en avons essentiellement trois types:

  • Collections simples de type set (ou bag-like), qui ne contiennent que quelques valeurs.
  • Collections de type tableau, qui ajoutent un index à côté des données.
  • Collections de type dict, qui ont des indices dans les données, c'est- k=>v dire des paires

Je suis devenu sceptique quant au comportement de type dict. Le canari dans la mine de charbon est map , où opérer sur des paires clé-valeur n'est pas naturel, car vous ne voulez généralement pas changer l'ensemble de clés. Il est également possible pour les tableaux et les dictionnaires d'implémenter la même interface:

  • keys correspond à eachindex
  • mapindexed et filterindexed seraient utiles pour les dictionnaires et les tableaux. Celles-ci sont comme la carte et le filtre sauf que vous transmettez également à votre fonction l'index de l'élément en question.
  • Les tableaux et les dictionnaires (et les tuples nommés, et peut-être d'autres choses) pourraient utiliser un itérateur pairs , qui est essentiellement un raccourci pour zip(keys(c), values(c)) .

Pensez à renommer ind2sub et sub2ind , qui sont apparemment des matlabismes, et qui ont un nom non julien et étrange pour un utilisateur non-matlab. Les noms possibles seraient respectivement indice et linearindice . Je n'ai pas osé faire un PR car je ne suis pas sûr de ce que les gens en pensent, mais je le ferai s'il y a du soutien.

Même chose avec rad2deg et deg2rad .

Réf. # 22791 ( select -> partialsort ). Meilleur!

Une chose que je n'ai pas vue ici: les arguments positionnels optionnels passent-ils en premier ou en dernier? Parfois, les arguments positionnels optionnels passent en premier, comme dans sum(f, itr) et rand([rng,] ..) . Mais ailleurs, ils vont en dernier, par exemple dans median(v[, region]) ou split(s::AbstractString[, chars]) . Parfois, ils peuvent passer en premier ou en dernier, mais pas les deux! (Par exemple, mean peut prendre une fonction en premier ou une dimension en dernier, mais pas les deux.)

La sémantique actuelle du langage force les arguments optionnels à passer en dernier: vous pouvez écrire f(a, b=1) mais pas f(b=1, a) . Mais si tous les arguments optionnels passent en dernier, qu'arrive-t-il aux blocs do pratiques?

Si rien d'autre, c'est une verrue mineure que le langage doit définir des méthodes comme ça, à partir de rand.jl : shuffle!(a::AbstractVector) = shuffle!(GLOBAL_RNG, a) . La syntaxe de l'argument positionnel facultatif doit prendre en compte exactement ce cas d'utilisation.

Peut-être devrait aller dans un autre problème, mais il semble possible de déplacer les arguments optionnels où vous le souhaitez. Par exemple, f(a = 1, b, c = 2) définirait f(x) = f(1, x, 2) et f(x, y) = f(x, y, 2)

xref # 22460 pour une tentative (impopulaire) d'activer les arguments par défaut à n'importe quelle position.

Peut-être renommer warn en warning (matlab l'utilise également), ce n'est pas grave, mais je pensais que je le mentionnerais?

J'aime warn car c'est un verbe, comme throw .

Je suis juste très confus par ceci:

julia> f(;a=1,b=1) = a+b                                                                                                                              
f (generic function with 1 method)                                                                                                                    

julia> f(a=4,5)            # I intended to write f(a=4,b=5)                                                                                                                           
ERROR: MethodError: no method matching f(::Int64; a=4)                                                                                                
Closest candidates are:
  f(; a, b) at REPL[13]:1

Je suggère de n'autoriser que les mots-clés en dernier lors de l'appel de fonctions, comme lors de la définition des fonctions de mots-clés.

Je suggère de n'autoriser que les mots-clés en dernier lors de l'appel de fonctions, comme lors de la définition des fonctions de mots-clés.

Il existe de nombreuses API où passer des mots-clés à d'autres positions est à la fois utile et ergonomique.

Par curiosité, existe-t-il un ordre d'évaluation défini pour les arguments positionnels et mots-clés? J'ai vu quelques problèmes plus anciens et https://docs.julialang.org/en/latest/manual/functions/#Evaluation -Scope-of-Default-Values-1 parle de portées, mais rien de ce que j'ai trouvé n'indique si par exemple les arguments sont évalués de gauche à droite, ou tous les arguments de position sont évalués avant tous les arguments de mot-clé, ou s'il n'y a pas d'ordre d'évaluation défini (ou autre chose).

@yurivish , pour les mots clés, voir la documentation (également https://github.com/JuliaLang/julia/issues/23926). Pour les options facultatives, l'histoire est un peu plus compliquée, peut-être lisez ici . (Notez cependant que les questions sont mieux posées sur https://discourse.julialang.org/)

Cela ne semble pas valoir son propre problème, mais il me semble toujours étrange que bits(1) renvoie un String , il semble que cela devrait être BitVector ou Vector{Bool} .

Je voudrais suggérer un examen des tables de méthodes qui distribuent sur Function ou Callable . Essayons de nous assurer que ces API sont comme nous les voulons… et que nous ne manquons pas une opportunité de permettre la restructuration des tables de telle sorte que nous pourrions autoriser l'appel du canard à n'importe quel objet.

Comme pour all et any , ceux-ci semblent faciles à déconseiller à all(f(x) for x in xs) , qui est déjà réduit à all(Generator(f, xs)) et ne devrait donc pas avoir de frais généraux.

Je ne sais pas si c'est ce que vous vouliez dire , mais je me suis dit que ça vaut indiquant juste au cas où: Je suis hardcore contre désapprouver toutes les API de type fonctionnel pour les générateurs. Nous avons any(f, x) et all(f, x) et ils sont largement utilisés; -10000000 pour supprimer ceux-ci (ou toutes ces méthodes, vraiment).

Je suis un générateur pro. Cela semble être un élément fondamental de la programmation paresseuse et il devrait être exporté. all(Generator(f, xs)) parfois plus pratique pour les fonctions définies que all(f(x) for x in xs) . Également +10000000 pour rétablir l'équilibre

Je préfère avoir moins de syntaxe ici si possible. S'il est facile, lisible et performant d'exprimer l'idée de "prendre xs, appliquer f à tout et retourner vrai si tout cela est vrai", alors pourquoi devrions-nous le mettre en un seul verbe?

Si le problème est le nom inutile x dans f(x) for x in xs , alors la suggestion de @bramtayl d'exporter Generator (peut-être en utilisant un meilleur nom comme Map ?) logique.

Des choses comme all(isnull, x) sont assez simples que all(isnull(v) for v in x) . Nous avons déprécié la fonction allnull de NullableArrays en faveur de all(isnull, x) ; si cette syntaxe disparaissait, nous devrons probablement la réintroduire.

Que diriez-vous de renommer strwidth en stringwidth (je pense que c'est la seule fonction de manipulation de chaîne exportée qui a abrégé la chaîne en str)

En fait, il a été renommé en textwidth (https://github.com/JuliaLang/julia/pull/23667).

OMI, ce problème est trop vaste pour avoir le jalon 1.0, étant donné que nous voulons arriver à geler les fonctionnalités sous peu. Nous pouvons avoir besoin de plusieurs propriétaires et attribuer des ensembles de fonctions pour examen si nous devons le faire.

En outre, c'est un autre endroit où FemtoCleaner peut mettre à jour automatiquement de nombreuses choses après la version 1.0, même si ce serait bien de bien faire les choses.

juste un commentaire sur textwidth:

INFO: Testing Cairo
Test Summary:   | Pass  Total
Image Surface   |    7      7
Test Summary:   | Pass  Total
Conversions     |    4      4
Test Summary:   | Pass  Total
TexLexer        |    1      1
WARNING: both Compat and Cairo export "textwidth"; uses of it in module Main must be qualified
Samples        : Error During Test
  Got an exception of type LoadError outside of a <strong i="6">@test</strong>
  LoadError: UndefVarError: textwidth not defined
  Stacktrace:
   [1] include_from_node1(::String) at .\loading.jl:576
   [2] include(::String) at .\sysimg.jl:14
   [3] macro expansion at C:\Users\appveyor\.julia\v0.6\Cairo\test\runtests.jl:86 [inlined]
   [4] macro expansion at .\test.jl:860 [inlined]
   [5] anonymous at .\<missing>:?
   [6] include_from_node1(::String) at .\loading.jl:576
   [7] include(::String) at .\sysimg.jl:14
   [8] process_options(::Base.JLOptions) at .\client.jl:305
   [9] _start() at .\client.jl:371
  while loading C:\Users\appveyor\.julia\v0.6\Cairo\samples\sample_pango_text.jl, in expression starting on line 28

Le message d'erreur semble assez clair sur quel est le problème? Cairo besoin d'étendre la méthode de base.

help?>  Base.textwidth
  No documentation found.

  Binding Base.textwidth does not exist.

julia> versioninfo()
Julia Version 0.6.0
julia> Compat.textwidth
textwidth (generic function with 2 methods)

Je n'avais aucun doute que le message (que j'ai reçu via travis) est OK, mais pourquoi Compat exporte-t-il textwidth s'il n'est pas en 0.6?

Parce que c'est le but de Compat? Cette discussion est cependant très hors de portée, donc je suggère que nous puissions la poursuivre sur le discours ou le relâchement.

Je suggère de changer copy en shallowcopy et deepcopy en copy car il m'a récemment fallu un certain temps pour réaliser que la copie est une copie "superficielle" et le La fonction que j'ai écrite faisait muter le tableau des tableaux. Je pense que ce serait beaucoup plus intuitif si copy faisait une copie "profonde" et quelque chose comme shallowcopy est utilisé pour les copies superficielles? Je sais maintenant quand utiliser deepcopy , mais je pense que de nombreux autres utilisateurs rencontreront le même problème.

Essayons s'il vous plaît de garder ce problème pour la cohérence de l'API, et non pas un sac à dos de "choses spécifiques que je n'aime pas".

Associative le nom semble assez incohérent avec tous les autres noms de type dans Julia.

Premièrement, les types ne sont généralement pas des adjectifs. Le nom est Association , et est utilisé au moins par certains documents Mathematica que j'ai trouvés.

Je pense que AbstractDict serait beaucoup plus cohérent avec d'autres types comme AbstractArray , AbstractRange , AbstractSet et AbstractString , chacun ayant a types de béton prototypiques Dict , Array , Range , Set et String .

Nos types d'exceptions sont un peu partout: certains sont nommés FooError , d'autres sont nommés BarException ; quelques-uns sont exportés, la plupart ne le sont pas. Cela pourrait utiliser un transfert pour la cohérence.

Alors, quelle serait la préférence, FooError ou BarException ? Exporté ou pas?

Pour moi, BarException implique quelque part un modèle de relance / capture.

Je préfère beaucoup, et d'autres dans le monde fonctionnel aussi, utilisent le modèle Some / None (*) où le flux de contrôle est plus direct et prévisible.

Donc +1 pour FooError

(*) Some / Void ex Optional dans Julia # 23642.

Ces choses sont-elles toujours sur la table étant donné le gel des fonctionnalités? J'aimerais particulièrement aborder les arguments optionnels par rapport aux arguments de mot-clé, mais la liste des fonctions avec plusieurs arguments optionnels (le cas le plus clair pour utiliser des arguments de mot-clé à la place) est assez longue.

S'il vous plaît, jetez un oeil! Je n'ai pas eu la chance de traiter systématiquement ces problèmes.

BTW, j'ai noté une incohérence dans la dénomination des traits: nous avons iteratorsize , iteratoreltype , mais IndexStyle , TypeRangeStep , TypeArithmetic et TypeOrder . On dirait que les variantes de CamelCase sont plus nombreuses et plus récentes, alors peut-être devrions-nous adopter cette convention partout?

Ceux-ci devraient certainement être cohérents. Voulez-vous faire un PR?

Je pense que cela devrait être corrigé dans le cadre de https://github.com/JuliaLang/julia/pull/25356.

EDIT: voir aussi https://github.com/JuliaLang/julia/issues/25440

Ceci est principalement fait ou peut être fait dans les versions 1.x. Je peux mettre à jour les cases à cocher, mais nous les avons simplement passées en revue lors de l'appel de triage et tout sauf # 25395 et l'audit de soulignement sont terminés.

Audit de soulignement

Voici une analyse de tous les symboles exportés à partir de Base qui contiennent des traits de soulignement, ne sont pas obsolètes et ne sont pas des macros de chaîne. La principale chose à noter ici est que ce sont uniquement des noms exportés; cela n'inclut pas les noms non exportés que nous disons aux gens d'appeler qualifiés.

J'ai séparé les choses par catégorie. Espérons que ce soit plus utile que gênant.

Réflexion

Nous avons les macros suivantes avec les fonctions correspondantes:

  • [] @code_llvm , code_llvm
  • [] @code_lowered , code_lowered
  • [] @code_native , code_native
  • [] @code_typed , code_typed
  • [] @code_warntype , code_warntype

Quelle que soit la modification appliquée aux macros, le cas échéant, elle doit être appliquée de la même manière aux fonctions.

  • [x] module_name -> nameof (# 25622)
  • [x] module_parent -> parentmodule (# 25629, voir # 25436 pour une précédente tentative de changement de nom)
  • [x] method_exists -> hasmethod (# 25615)
  • [x] object_id -> objectid (# 25615)
  • [] pointer_from_objref

pointer_from_objref pourrait peut-être faire avec un nom plus descriptif, peut-être quelque chose comme address ?

Alias ​​pour l'interopérabilité C

Les alias de type contenant des traits de soulignement sont C_NULL , Cintmax_t , Cptrdiff_t , Csize_t , Cssize_t , Cuintmax_t et Cwchar_t . Ceux qui se terminent par _t devraient rester, car ils sont nommés pour être cohérents avec leurs types C correspondants.

C_NULL est l'étrange ici, étant le seul alias C contenant un trait de soulignement qui n'est pas reflété en C (puisque dans C c'est juste NULL ). Nous pourrions envisager d'appeler cela CNULL .

  • [] C_NULL

Comptage de bits

  • [] count_ones
  • [] count_zeros
  • [] trailing_ones
  • [] trailing_zeros
  • [] leading_ones
  • [] leading_zeros

Pour une discussion sur la façon de les renommer, voir # 23531. Je suis très favorable à la suppression des traits de soulignement pour ceux-ci, ainsi que de certains des remplacements proposés dans ce PR. Je pense que cela devrait être reconsidéré.

Opérations dangereuses

  • [] unsafe_copyto!
  • [] unsafe_load
  • [] unsafe_pointer_to_objref
  • [] unsafe_read
  • [] unsafe_store!
  • [] unsafe_string
  • [] unsafe_trunc
  • [] unsafe_wrap
  • [] unsafe_write

Il est probablement acceptable de les conserver tels quels; la laideur du trait de soulignement souligne encore leur insécurité.

Indexage

  • [] broadcast_getindex
  • [] broadcast_setindex!
  • [] to_indices

Apparemment, broadcast_getindex et broadcast_setindex! existent. Je ne comprends pas ce qu'ils font. Peut-être pourraient-ils utiliser un nom plus descriptif?

Fait intéressant, la version d'index unique de to_indices , Base.to_index , n'est pas exportée.

Traces

  • [] catch_backtrace
  • [x] catch_stacktrace -> stacktrace(catch_backtrace()) (# 25615)

Ce sont probablement les équivalents de bloc catch de backtrace et stacktrace , respectivement.

Tâches, processus et signaux

  • [] current_task
  • [] task_local_storage
  • [] disable_sigint
  • [] reenable_sigint
  • [] process_exited
  • [] process_running

Ruisseaux

  • [] redirect_stderr
  • [] redirect_stdin
  • [] redirect_stdout
  • [x] nb_available -> bytesavailable (# 25634)

Ce serait bien d'avoir une fonction de redirection IO -> IO plus générale dans laquelle tout cela pourrait être combiné, par exemple redirect(STDOUT, io) , supprimant ainsi à la fois les traits de soulignement et les exportations.

Promotion

  • [] promote_rule
  • [] promote_shape
  • [] promote_type

Voir # 23999 pour une discussion pertinente concernant promote_rule .

Impression

  • [x] print_with_color -> printstyled (voir # 25522)
  • [] print_shortest (voir # 25745)
  • [] escape_string (voir # 25620)
  • [] unescape_string

escape_string et unescape_string sont un peu bizarres en ce sens qu'ils peuvent imprimer dans un flux ou renvoyer une chaîne. Voir # 25620 pour une proposition de déplacer / renommer ces derniers.

Chargement du code

  • [] include_dependency
  • [] include_string

include_dependency . Est-ce même utilisé en dehors de Base? Je ne peux pas penser à une situation où vous voudriez cela au lieu de include dans un scénario typique.

include_string . N'est-ce pas juste une version officiellement sanctionnée de eval(parse()) ?

Les choses que je n'ai pas pris la peine de catégoriser

  • [x] gc_enable -> GC.enable (# 25616)
  • [] get_zero_subnormals
  • [] set_zero_subnormals
  • [] time_ns

get_zero_subnormals et set_zero_subnormals pourraient faire avec des noms plus descriptifs. Doivent-ils être exportés?

+1 pour method_exists => methodexists et object_id => objectid . C'est aussi un peu idiot que catch_stacktrace existe même. Il peut être déconseillé à sa définition, stacktrace(catch_backtrace()) .

Que pensons-nous de la suppression du soulignement de C_NULL ? Je m'y suis habitué, mais j'achète aussi l'argument qu'aucun des autres noms C* n'a de trait de soulignement.

Les autres noms C sont des types, tandis que C_NULL est une constante. Je pense que c'est bien comme ça et suit les directives de dénomination.

et suit les directives de dénomination.

Comment?

Les constantes sont souvent toutes en majuscules avec des traits de soulignement - C_NULL suit cela. Comme @ iamed2 l'a dit, c'est une valeur, pas un type, donc la convention de dénomination Cfoo ne s'applique pas nécessairement.

J'ai pensé à tort que https://github.com/JuliaLang/julia/blob/master/doc/src/manual/variables.md#stylistic -conventions faisait référence à des constantes, mais ce n'est pas le cas. Cela devrait probablement.

Je suggère une interface cohérente et mathématiquement solide pour les espaces de Hilbert généraux dans lesquels les vecteurs ne sont pas des tableaux de Julia. Les noms de fonctions comme vecdot , vecnorm , etc. pourraient bien être remplacés par les concepts généraux de inner et norm comme discuté dans https: // github. com / JuliaLang / julia / issues / 25565.

Comme je l'ai dit à quelques reprises, ce n'est pas une question fourre-tout pour les choses que l'on veut changer.

Je crois que les seuls éléments restants sous ce parapluie pour 1.0 sont les # 25501 et # 25717.

J'aimerais faire quelque chose avec (get|set)_zero_subnormals mais peut-être que la meilleure solution à court terme est de simplement les annuler.

Quelque chose qui devrait probablement revoir est la façon dont les nombres sont traités dans le contexte des opérations de collecte comme map et collect . Il a été souligné que le premier renvoie un scalaire mais que le second renvoie un tableau 0D.

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

Questions connexes

StefanKarpinski picture StefanKarpinski  ·  3Commentaires

felixrehren picture felixrehren  ·  3Commentaires

Keno picture Keno  ·  3Commentaires

yurivish picture yurivish  ·  3Commentaires

helgee picture helgee  ·  3Commentaires