nitlang / nit Goto Github PK
View Code? Open in Web Editor NEWNit language
Home Page: http://nitlanguage.org
License: Apache License 2.0
Nit language
Home Page: http://nitlanguage.org
License: Apache License 2.0
La méthode "==" apparaît deux fois dans la liste Methods de la sidebar Properties mais référence la même méthode:
Il n'y pas de documentation et d'exemple pour cette redéfinition de méthode.
Redefine documentation:
Have self and other the same character at the same index for all indexes of the two string.
Example:
assert ("hello" == "bonjour") == false
assert ("hello" == "Hello") == false
assert ("hello" == " hello ") == false
assert ("hello" == "hello") == true
Each time an intern or extern object is allocated, the native representation is left as it (memory leak) when the Nit part of the object is garbage collected.
Example:
for i in [0..10000000[ do
var s = i.to_s
end
stdin.read_line # pause and look at the mem footprint (with top for instance)
Un bug est apparu dans la page initiale de stdlib:
http://nitlanguage.org/doc/stdlib/
Le module socket ainsi que d'autres modules ont été dupliqués.
idée 1, une fermeture est un argument comme les autres:
idée 1bis:
idée 2, on cherche sans mot clé:
exemple sort:
mon_array.sort xxx a,b do yyy a <=> b
exemple fichier:
read_file "/etc/passwd",
xxx file do
print file.read_all
end
ou
read_file "/etc/passwd", xxx file do
print file.read_all
end
exemple fichier avec erreur:
read_file "/etc/passwd",
xxx file do
print file.read_all
end,
xxx ioerror do
print "cannot open: {ioerror}"
end
exemple itérateur:
mon_array.iterate xxx e do print e
Est-ce que ca vaut le coupr de garder les fermetures en anvant uniquement.
Si oui, avons-nous besoins de fermetures normales en plus ?
Si oui, quelle est la syntaxe de ces fermetures normales et comment éviter que les gens mélanges les fermetures normales des fermetures en avant.
Une idée serait de baser la différence sur le mot clé xxx.
Proposition:
for
fun
Exemple:
mon_array.sort for a,b do yyy a <=> b
var b = new Button(
fun(event, button) do
if event isa MouseDoubleClic then
button.blink = true
yyy true # the event was catched
end
yyy false # the event was uncatched
end)
C'est parfois pratique d'avoir des paramètres par défaut.
En Nit, une idée serait de profiter de l'annotation nullable pour savoir si un type un paramètre est nécessaire. ainsi, un paramètre nullable non renseigné prends la value null. C'est à l'implémentation d'attraper ce null pour en faire ce qu'elle veut.
fun toto(base: Int, delta: nullable Int, factor: nullable Int)
do
if delta == null then delta = 0
if factor == null then factor = 1
print (base + delta) * factor
end
toto(10) # outputs 1
toto(10, null) # same
toto(10, null, null) # same
toto(10, 20) # outputs 30
toto(10, 20, null) # same
toto(10, 20, 40) # outputs 120
C'est simple (KISS) : à la fois pour l'utilisateur et pour le développeur
C'est expressif : La valeur par défaut peut être déterminée de façon programmative (ce n'est pas une bête constante)
C'est encapsulé : La valeur par défaut peut être un détail d'implémentation
C'est évolutif : Une redéfinition peut changer la valeur par défaut si elle le souhaite
C'est facile à implémenter : Ajouter des null
pour les arguments manquants lors de l'invocation.
Est-ce que tout les paramètres nullables veulent vraiment dire «valeur par défaut» ?
Exemple
interface Toto
fun set_something(something: nullable Something) is abstract
end
...
var t: Toto = ...
t.set_something # le set_something sans argument est bizarre
# surtout que la sémantique est en réalité `unset_something`
Non-Exemple
var a = new Array[nullable Object]
a.add # refusé car meme si `null` est accepté ici,
# la signature de la méthode ne prends pas
# explicitement un nullable, mais un E.
# Donc il est raisonnable de dire qu'on pas droit
# aux paramètres par défaut dans ce cas
print x
and
print "{x}"
Throw a compile error if x
is nullable.
The first solution is to add a cast:
print x.as(not null)
and
print "{x.as(not null)}"
But this code will crash at runtime if the cast fails.
If we want to display an empty string or something else, a possible workaround is to use or else
when x
is a string.
print(x or else "(null)")
In more complexe cases, where x
is not a string.
For exemple, if x
is a Bar
, the typing system of Nit will crash because it doesn't find static type for the expression x or else ""
: Type Error: ambiguous type Toto vs String.
It's a problem related to types union.
The solution is then to upcast to Objet.
It's dirty, and throws a compile warning but it works:
print(x or else "(null)".as(Object))
Quelle représentation utiliser lors des différentes phases de compilation ?
État des lieux:
astprinter
et astvalidation
commencent a être développésastbuilder
d’instanciation et transformationtransform
transforme une partie des noeuds compliqués en des noeuds plus simplsAvantages:
Inconvénients:
Avantages:
Inconvénients:
s'arranger pour que les éléments de l'ast spécialisent des trucs plus simples utilisable comme truc intermédiaires. C'est un peu ce qui a été fait dans PRM.
C'est pas vraiment bloquant pour la v1.0 car ce qui importe c'est d'avoir la spec et la lib standard stable, mais avoir un seul méta-modèle serait plus simple.
on considère niti, nitg et nitstats comme techologie-preview. Ca veut dire que les solutions aux bloqueurs de [[v1.0]] doivent être implémentés avec le vieux métamodèle.
on passe le compilo séparé actuel en déprécié.
Ça veut dire qu'il faut :
note: ici le pb est illustré avec des types virtuels mais le meme problème existe avec des types parametriques formels (de la généricité covariante).
Soit le programme suivant:
# définitions
class A
type V: X
fun foo(v: V...) do ...
end
class B
super A
redef type V: Y
end
Quel est la sémantique de
# appel
var a: A = new B
var y = new Y
a.foo(y)
On a envie que le code précédent passe.
En effet bien que l'appel soit covariant, il n'y a pas vraiment d'erreur de type ici:
Toutefois, le principe actuel des varargs de Nit c'est que le parametre v est initialisé avec un Array contenent les arguments.
Plus précisément, le code précédent est actuellement equivalent pseudo code suivant:
# definition
fun foo(v: Array[V]) do ...
...
# appel
var varargs = new Array[X] # X parceque statiquement Array[V] pour A est un Array[X]
varargs.add(x)
a.foo(varargs)
Or, la signature de foo utilise un type virtuel, donc l'appel est non sur.
On ajoute donc un test de type dynamique à foo qui vérifie que l'appel est correct.
Au niveau de l'implémentation, foo resemble à:
# définition
fun foo(v) do
assert v isa Array[V] # test implicite car l'appel a foo est non-sur
Dans le cadre d'un receveur de type dynalique B, le test de type exécuté est donc
assert v isa Array[Y]
Or l'agrument est un Array[X] donc le test devrait échouer
Il semble que le vrai problème soit le new Array[...]
implicite associé au varargs.
En effet, le type concret utilisé est statiquement déterminé sans tenir compte de type dynaique du receveur.
Ce qui fait qu'en cas de covariance, on est garanti d'échouer le test.
La solution pourrait donc consister a rendre le new Array plus dyamique
Au lieu de faire varargs = new Array[X]
, faire varargs = new Array[a.V]
Problèmes:
varargs.add
implicite bien avant l'appel à foo (c'est pas très grave mais c'est incohérent avec la covariance sans varargs)Au lieu de faire un varargs = new Array[X]
faire un varargs = a.new_varargs_foo
où new_varargs_foo est une fonction cachée implicite qui serait automatiquement introduite pour chaque méthode covariante sur un vararg et automatquement redéfinie en cas de redéfinition du type virtuel
Problèmes
C'est l'invoqué qui construit le vararg et non l'invoquant.
Concrètement, cela voudrait dire qu'il faudrait passer les argument multiples à foo par autre chose qu'un Array puis laisser foo construire le vrai vararg dans foo.
Donc, en admétant l'existence d'un moyen au runtime de passer des arguments multiples, le pseudo-code généré serait quelque chose comme
# définition
fun foo(vs: V[]) do # machin magique pour passer les varargs
var v = new Array[V]
for (i=0; i<vs.length; i++) {
assert vs[i] isa V
v.add(vs[i])
}
...
...
# appel
a.foo(y) # machin magique pour passer les varargs
Problèmes:
Ce machin magique pourrait etre un Array[nullable Object]. On aurait donc
# définition
fun foo(raw_varargs: Array[nullable Object]) do
var v = new Array[V] # vrai varargs
for x in raw_varargs do
assert x isa V
v.add(x)
end
...
...
# appel
var raw_varargs = new Array[nullable Object] # toujours nullable Object
varargs.add(x)
a.foo(raw_varargs)
C'est pas super mais ca marcherait
Note: un bete var v = raw_varargs.as(Array[V])
ne marche pas car un cast n'est pas une migration d'instance. Dit autrement. raw_varargs.class_name == "Array[Object]", cast ou pas cast.
Note2: Toutefois, en admettant l'existance d'une migration d'instance on pourrait avoir var v = raw_args.change_class(Array[V])
mais ca serait exagéré et cause de souffrances infinies en cas de spécialisation de bornes de classes génériques.
La méthode équivalente en C# (Delete (string path) : void) se trouve dans une classe dédié plus adaptée à la gestion des fichiers : System.IO.File. Il faudrait déplacer cette méthode dans le module "file".
La méthode C# utilise des exceptions pour indiquer la cause de la non suppression du fichier. Faire de même en Nit permettrait d'obtenir plus d'information sur la raison de la non suppression.
L'union des classes et des interfaces sous la classe MClass semblait pratique au début mais finalement je me suis retrouvé pris à plusieurs reprises à pas pouvoir mettre de code dans mclass spécifique au cas des classes et/ou des interfaces.
Si MClass permet de réifier une classe quelque soit sont évolution par le rafinement (abstraite ou non) il ne me semble pas en revanche qu'une interface puisse changer de statut, elle pourrait donc être découplée de classe par exemple comme ça :
class MEntity (l'actuelle MClass)
class MClass (nouvelle classe dans laquelle on descend les trucs spécifiques aux classes)
super MEntity
class MInterface
super MEntity
Etre obligé de passer par MClassType pour récupérer les définitions de "super" c'est un peu chiant. Il faudrait ajouter une méthode dans MClassdef qui retourne les déclarations de super faite dans la class def.
mclassdef.in_hierarchy.greaters
contient le mclassdef lui-même, c'est assez chiant de devoir tester à chaque fois si mclassdef == mclassdef_greater. Pareil pour smallers.
solution? : ajouter un strict_greaters
(et strict_smallers
)
Je reste déterminé à penser l'idée d'une interface entre le méta-modele et le programme fournissant une vue basée sur le mainmodule reste très intéressante et ferait sauver un grand nombre de ligne de code en évitant au développeur de se battre avec des classdef. Je vais rassembler tous les bouts de code qui traînent et t'en proposer une.
Il manque des fonctionnalités comme parents direct, descendants directs.... Ce genre de chose pourrait aller dans la vue du métamodèle basée sur le main module.
Grace aux nullables, un attribut non nullable possède une sémantique claire :
D'un point de vue implémentation, c'est facile : on utilise NULL pour représenter un attribut non initialisé.
Ca marche parce que un attribut nullable ne peut pas etre non-initialisé et statiquement on peut savoir si on est :
En fait c'est pas si évident (sinon il y aurait pas cette page)
Ici j'utilise des types générique mais on peut faire la même chose avec des types virtuels.
class G[E: nullable Object]
var attr: E
init foo(e: E) do self.attr = e
init bar(e: E) do print self.attr
init baz(e: E) do end # on initialise pas attr
end
# Comment se comportent foo, bar et baz dans les cas suivants?
var g1 = new G[nullable Object].foo(1)
var g2 = new G[nullable Object].bar(2)
var g3 = new G[nullable Object].baz(3)
var g11 = new G[Object].foo(11)
var g12 = new G[Object].bar(12)
var g13 = new G[Object].baz(13)
# hum, c'est pas évident, là, non ?
Implémenter les attributs non initialisés avec NULL marche bien quand les attributs sont des pointeurs.
Maintenant, un compilateur optimisant pourrait souhaiter représenter certains types primitifs directement dans l'objet.
class Truc
var chose: Bool = false # Veut-on vraiment du boxing/tagging ? franchement ?
end
Naivement, on pourrait penser que dans le cas précédent, il n'y a pas de difficulté à ne pas représenter l'état "non-initialisé" de l'attribut chose
.
Or, l'exemple suivant montre qu'en fait cela n'est pas si trivial.
class Truc2
var machin: Bool = self.chose
var chose: Bool = false # Meme cas qu'au dessus?
end
En effet, la spec du langage dit que la valeur par défaut des attributs est évaluée dans l'ordre.
Donc, au moment d'évaluer la valeur par défaut de machin
, chose
n'a pas de valeur et le bon comportement serait donc d'avorter.
Aïe aïe aïe, pas si simple à optimiser finalement.
L'exemple est bien nul pourtant.
Exemple:
stream_with_host(thost: String, tport: Int, timeout:Int)
Si ya bien une killer feature dans Nit c'est le typage adaptatif.
Le pb est la spécification du typage dans les cas de boucles.
Actuellement, la regle assez compliquée et assez limitative.
Le danger serait d'avoir une règle expressive mais trop complexe.
On peut mesurer la complexité de la règle par la taille de la doc dans la spec + la taille du message d'erreur
Ce que l'on voudrait c'est une règle simple et expressive.
On voudrait aussi un règle qui fait que les cas sont stables.
Actuellement, le prog suivant est refusé
var x: A
x = new B
while ... do
# zone 1
x = new A # Erreur x est statiquement un B
end
En effet, qui nous dit que dans la zone 1 on ne profite pas du fait que a est statiquement un B ?
Il faudrait refaire un tout pour vérifier.
On en toute généralité le nombre de tours semble etre infini puisque que le nombre de type statique est infini (TODO: trouver un exemple ou x passe par une infinité de types)
Changer la syntaxe de abort pour accepter un message d'arrêt. Voir pour forcer un message.
Présentement, abort arrête purement et simplement le programme et affiche un beau
aborted on file:line.
La plupart du temps, quand on ne connait pas par coeur le code à l'origine du abort, on est obligé d'aller voir dans file:line pour savoir la cause du abort.
Dans de rares cas, le développeur est sympa et fait un print juste avant pour en expliquer la cause :
if something then
print "message"
abort
end
L'idée serait de permettre le passage d'un string à abort permettant d'afficher un message juste avant le abort :
abort("mon message")
Donnerait à l'exécution :
aborted on file:line
cause: mon message
Reste une décision à prendre, le message est-il ou non obligatoire. S'il est obligatoire le contre que je vois c'est quo'n est obligé de passer un message pour tous les aborts mais c'est aussi un pour : plus de abort sans message explicatif.
Si le message est obligatoire, on va se retrouver avec du bruit et des choses du genre
abort ""
ou
abort "abort"
Si le message est optionnel, alors je vois pas vraiment de raison de penser qui les gens seront plus enclin d'écrire
abort "bla bla"
que
print "bla bla"
abort
Éventuellement, l'avantage serait toutefois que le "bla bla" est associé avec le abort par le moteur. Donc pourquoi pas.
Toutefois, il faut quand meme faire un lien avec le assert.
actuellement on fait quelque chose du genre
assert test else print "bla bla"
qui donne quelque chose comme
bla bla
runtime error: assert failed (monmodule.nit:22)
c'est pas top. quoi qu'on pourrait imaginer écrire plutôt
assert test else abort "bla bla"
ce qui est pas si mal, toutefois le lien entre le assert et le abort n'est pas si clair car donne
runtime error: abort (monmodule.nit:22)
cause: bla bla
le role du assert a disparu!
C'est en fait strictement équivalent a.
if not test then abort "bla bla"
Et c'est laid d'avoir un chose aussi strictement équivalent :(
Donc ca a l'air d'une bonne idée mais ca manque de finition
quelques stats (sur lib/ et src/) a coup de grep
Est-il légitime de généraliser string en une collection[char](voir sequence[char]) ?
skip
alternate
sort_filter
sort_with
uniq
seq_uniq
Il faut dupliquer les méthodes utiles dans String
On peut egalement proposer une vue sur la séquence de caracteres (chars
). C'est ce que fait Ruby:
for c in mastring do ... # refusé
for c in mastrinfg.chars do ... # accepté
L'idée, pondue a 2h du mat, est de fusionner [[!nitdoc Collection]] et [[!nitdoc SequenceRead]].
for
(et du iterate
) est déterministe et simplifiéelib/
, src/
et exemples/
sauf pour déclarer la classe et les deux sous-classes.toutes les collections actuelles sont déterministes donc ca pose pas vraiment de problème sémantique
en cas de future collection non déterministe, deux solutions 1: ne pas faire une collection; ou 2: préciser que l'itération est non déterministe dans la doc ; en effet, la loi du déterminisme n'est pas fondamentale.
Le problème 1 c'est l'opérateur []
qui peut être chiant.
En effet, par exemple, dans POSet, []
est redéfini pour retourner la vue sur un élément donné.
Une solution serait de renommer []
en at
dans collection avec []
comme synonyme dans Array et String seulement.
Une autre solution serait de renommer le []
de POSet.
test
L'objectif est d'améliorer la syntaxe des attributs dits « nouveaux-style » sans toucher à la sémantique.
avant, "var toto: Int" on peut lire toto de partout mais ya que le module qui peut y écrire
apres, "var toto: Int" ya que le module qui peut lire et écrire toto
Les attributs « vieux-style » (ie ceux de PRM):
Les attributs sont des propriétés publiques, protected ou privés et on peut à l'occasion demander d'avoir des accesseurs dynamiquement générés.
Les attributs nouveaux style (commit 7010c9d du 5 janvier 2011):
Les attributs ne sont plus accessibles par le programmeur et toute définition d'un attribut vient systématiquement avec un getter et un setter. Plus de détail dans la doc officielle.
readable
et writable
sont remplacés par getter
et setter
.Avant:
private var un = 1
var de = 2
var tr writable = 3
private var qu: Object
var ci: Object
var si: Object writable
Après:
var un = 1
var de = 2, getter
var tr = 3, getter, setter
var qu: Object
var ci: Object, getter
var si: Object, getter, setter
La modification des visibilités ou l'info d'un redef est faite avant le mot clé getter et setter.
Aucune visibilité ou redef n'est possible sur le mot clé var.
Avant:
interface A
fun toto: Int is abstract
end
class B
super A
redef var toto: Int protected writable
end
Après:
interface A
fun toto: Int is abstract
end
class B
super A
var toto: Int, redef getter, protected setter
end
On peut passer à la ligne après la virgule.
Avant:
protected var mon_attribut_compilque: UnType[Complique] protected writable = new UnSousType[CompliqueAussi](14)
Apres:
var mon_attribut_compilque: UnType[Complique] = new UnSousType[CompliqueAussi](14),
protected getter,
protected setter
On autorise à donner un nom après le mot clé getter ou setter
var empty: Bool, getter is_empty
Permet aussi de garder des noms simples avec des accesseurs compliquées
Avant:
private var real_attr: Type
fun attr: Type do return self.real_attr
fun attr=(a: Type)
do
foo(attr) # des trucs
self.real_attr = a
end
Après:
var attr: Type, getter, private setter intern_attr=
fun attr=(a: Type)
do
foo(attr) # des trucs
self.intern_attr = a
end
Ceci permet aussi d'écrire facilement des accesseur avec une signature différente
Exemple:
var toto: nullable Truc, getter safe_toto, setter
fun toto: Truc do return self.safe_toto.as(not null)
fun has_toto: Bool do return self.safe_toto != null
Ce qui donnera dans la doc:
fun has_toto: Bool
fun toto: Truc
fun toto=(toto: nullable Truc)
fun safe_toto: nullable Truc
plusieurs getter et setters peuvent être générées.
chacun est associée à un propriété globale distincte.
class A
var toto: Type,
getter,
private getter direct_toto,
setter,
private setter direct_toto=
end
class B # dans le même module que A
super A
redef fun toto: Type
do
var o = self.direct_toto
foo(o)
return o
end
redef fun toto=(o: Type)
do
bar(o)
self.direct_toto = o
end
end
En cas de besoin, chaque accesseur peut être documenté indépendamment en utilisant une notation multi-ligne
var toto: Type,
# doc du getter normal
getter,
# doc du getter direct
protected getter direct_toto
La séparation avec des , permet facilement d'ajouter posiblement de nouveaux mot-clés et modificateurs
Exemple de science-fiction:
var toto: Int, synchronized, notify foo, allocation in class, flavour = up
Idéalement, on voudrait un maximum d’indépendance entre :
En pratique, cette indépendance est globalement vraie sauf plus plusieurs trucs ad-hoc pas clair.
Exemple de liens entre spec et lib : les littéraux présupposent l’existence de classes (Int, Bool), voir de constructeurs ou méthodes spécifiques (Array#add
, String#from_native
), etc.
Exemple de liens entre compilateur et lib : le compilateur présume de l'implémentation de certaines méthodes primitives comme Int#+
Ce problème n'est pas original à Nit.
Dans de nombreux langages de haut niveau, la sémantique de certaines fonctions de la lib standard et connue du compilo qui en profite pour les optimiser.
Là où cela devient plus subtile avec Nit est la possibilité de raffiner certaines méthodes.
Est-ce que l'on veut vraiment autoriser la chose suivante, est si non, quel est l'argumentation pour le refuser.
redef class Int
redef fun %(o)
do
print "modulo"
super
end
end
Savoir si certaines choses sont dans la lib ou dans le compilateur est parfois compliqué.
Idéalement, on voudrait avoir tout dans la lib et un minimum de cas a gérer dans le compilo. C'est ce qui est fait dans Nit mais parfois de façon un peu compliqué.
Un exemple est la classe Array (tableaux redimensionnements).
La logique d'un Array est quasiment entièrement codée en Nit.
Un Array est implémenté avec un NativeArray qui est le tableaux primitif (non redimensionnable) et lea logique du enlarge du Array est codés en Nit.
Dit autrement, dans Array il y a tout ce qui peut être codé en Nit, et dans NativeArray il y a tout le reste.
Ce qui fait que toute la logique est dans Array et que NativeArray est une structure opaque très fragile.
La ou ça devient subtil, c'est comment le compilateur devrait gérer les tableaux au niveau des littéraux, au niveau des accès et au niveau des itérateurs.
Par opaque, chaque moteur d'exécution fait comme il veut.
Actuellement, on a:
Nit_NativeArray
qui contient une entete d'object Nit (pointeur vers table de classe, objectid) suivit d'un champ taille suivi de tous les éléments du tableau (sous forme de val_t).val_t*
. On a ajouté une entete devant uniquement pour que le GC puisse visiter les objets.Array[Int] -> int*
) cette fois ya vraiment rien, meme pas d'info sur la taillePar fragile, j'entends le fait que le comportement de NativeArray n'est pas déterminé dans la lib afin de laisser un max de latitude au compilateur.
Le problème c'est que cette non détermination est dangereuse. Par exemple la méthode NativeArray#[]=
peut réussir pour un index invalide et corrompre la mémoire du programme. C'est a partie Nit de la bibliothèque (cad la classe Array) qui s'occupe de garantir/vérifier que les accès sont valides.
En pratique, NativeArray n'est pas directement accessible au clients de la lib standard.
Quel est la sémantique de
var x = [10, 20, 30]
Ben, c'est pas clair.
Pour preuve le comilo séparé (et PRM original) implémentent la chose comme :
var x = new Array[Int]
x.add(10)
x.add(20)
x.add(30)
Alors que nitg et l'interpréteur nit font :
var tmp = calloc_array(3) # fonction interne qui retourne un NativeArray
tmp[0] = 10
tmp[1] = 20
tmp[2] = 30
var x = new Array.with_native(tmp, 3) # on répète 3
# parceque un NativeArray n'a pas de taille intrinsèque
Array est une classe très utilisée en Nit:
On a donc envie que pour les accès, l'implémentation soit la plus efficace possible.
Or en théorie un simple accès a[1]
var a: [10, 20, 30]
print a[1] # affiche 20
réalise les choses suivantes :
Sequence::[]
sur a
. (le []
de Array est introduit dans Sequence, une sous-classe de Collection)Array#[]
a
NativeArray::[]
sur a.native
NativeArray::[]
C'est pour cette raison que le compilateur séparé triche.
en effet, le compilateur séparé inline les méthodes Array#[]
et NativeArray[]
, ce qui invalide toute redéfinition de ces méthods dans des souclasses ou raffinements. mais en contrepartie on évite deux dispatchs et deux calls.
NOTE: PRM original avait une option --no_inline_primitive
qui forcait le dispatch pour toutes les méthodes (meme le Int#+
)
Page 187 de ma these on voit que le cout de cette option était assez importante.
le for/in
du Array combine les deux problème précédents.
var a = [10, 20, 30]
for i in a do print i
La spec dit qu'un for est une fermeture, c'est ce qu'implémentent le compilo séparé.
var a = [10, 20, 30]
a.iterate !each(i) do print i
PRM n'avait pas de fermeture, et le nouveau métamodèle (nitg et interpréteur) n'en n'a pas encore, donc le for est une boucle sur un itérateur (Note: en Nit un [[!nitdoc Iterator]] à 3 méthodes)
var a = [10, 20, 30]
var tmp = a.iterator
while tmp.is_ok do
var i = tmp.item
print i
tmp.next
end
En pratique, la version par itération est assez inefficace (allocation, dispatches, etc.).
Comme le Array#iterate
de la lib est efficace, la version fermeture est meilleure.
Toutefois, le code suivant est encore plus efficace:
var a = [10. 20, 30]
var idx = 0
while idx < a.length do
var i = a[idx]
print i
idx += 1
end
Donc, certains compilos implémentent un court-circuit pour le cas du for (et souvent du Range aussi) :
Array#iterate
qui contient en fait la version efficace est simplement inlinéune méthode "is intern" est finale et ne peut etre redéfinie.
avantage: la sémantique est claire et on a pas de mot clé "final" utilisable par l'utilisateur.
une nouvelle catégories de classes "intern" qui possède les memes restrictions que les enums/universals mais utilisés pour des choses comme NativeArray ou NativeString
un mot clé "primitive" pour annoter les choses (classes et méthodes) non internes mais quand meme connues du compilateur (String, iterate, Object#==). les méthodes "primitive" ne sont pas raffinables.
Un autre terme possible serait "builtin" (utilisé dans CLOS)
pas de gestion spécifique du code fragile (ex. NativeArray). On continue de le protéger avec de la visibilité.
Exemple:
redef class Array[Char]
fun foo(c: Char) do
print c.is_alpha
end
end
When compiling this code the compiler throws the error:
Error: Method 'is_alpha' doesn't exists in Array#0.
print c.is_alpha
Here the redef of Array with the formal type name Char must be rejected by the compiler because a type with the name Char already exists.
Assigned to @Morriar
Actuellement il y a 3 (environ) specification/implémentation d'interface native.
Question: qu'est-ce que qu'on veut pour la [[v1.0]] ?
Idéalement, il faudrait :
Modifications à apporter à l'interface legacy
With NIT_DIR env var setted to /home/ME/nit/, when I compile from /nit/bin like:
nitg something.nit
I get a compile error saying that there is a module conflict between /home/ME/nit/lib/module1.nit and ../lib/module1.nit.
assigned-to: @Morriar
La méthode "chaine1.CompareTo(chaine2) : Int" de C# permet de faire le travail de comparaison lexicographique des deux de NIT : "<" et "==".
0 : la première chaîne vient après la deuxième.
Je propose d'utiliser "<" et "==" pour créer une méthode équivalente: "compare_to(s: String): Int". Cela permettrait d'obtenir le résultat plus rapidement lorsque l'on ne connaît pas le contenu des chaînes comparées.
Les constructeurs c'est horrible :
Les constructeurs en héritage multiple sont problématiques à plusieurs niveaux :
en Nit, il y a en plus plein de magie pour "faciliter" le dev. mais en pratique c'est affreusement compliqué. le nouveau MM ne simule qu'une partie de la magie d'ailleurs.
Plusieurs approche restrictives ont été proposés pour guider/controle les constructeur afin d'éviter un maximum de probleme. Or il semblerait que cela ne marce pas pour un raison très simple: trop de restriction force le programmeur a les contourner de facon plus laide encore.
Les constructeurs ne sont que des méthodes comme les autres.
Le fait que 'make' (Eiffel) ou 'initialize' (Ruby) ait principalement un role de constructeur est une convention du programmeur (Eiffel) ou de la libstd (Ruby)
La gestion du cas multiple doit être pensée par le programmeur, tout comme pour le reste des méthodes.
Pro: KISS
Con: risque d'invocation répétée en Eiffel, forte fragilité de code Ruby qui place des constructeurs dans des mixins.
Les constructeurs de C++ sont très compliqués.
Peu nombreux sont ceux qui maîtrisent toutes les subtilités.
Pro: aucun
Con: tous (ou presque)
Spécialisation simple, les chanceux.
Le problème est principalement:
Ce pb s'illustre simplement HM. soit le losange ABCD.
Localement B et C pourraient initialiser le A avec un constructeur différent/un argument différent => on a un conflit sémantique: quelle est la volonté du programmeur.
S'il n'y avait qu'un constructeur sans paramètre, une bête linéarisation ferait le travail.
C'est qui se passe an Nit avec les valeurs par défaut des attributs.
Si la sous-classe déterminait elle même comment invoquer tous les super-constructeurs il n'y aurait pas non plus ce problème; c'est ce qu'essaye de faire le système de C++; mai ca pose plein d'autres problèmes (encapsulation brisée, courtcircuitage, complexification des classes profondes)
Forcer un super
(ou au moins un truc explicite) un début des constructeurs est relou.
C'est toutefois la norme dans plusieurs langages (Eiffel, Ruby, CLOS) principalement parque que les constructeurs sont des méthodes comme les autres et que les méthodes comme les autres n'ont pas de super
implicite au début.
C'est standard en CLOS (via :initarg
) et automatique en Nit dans les cas simples.
Toutefois dans les cas moins simples on y a pas droit et c'est dommage.
En Eiffel et Ruby, les constructeurs sont hérités (comme toute méthode).
En Eiffel, le role de constructeur (cad la possibilité d'utiliser un Create
(cad new
)) doit être donné (ou redonné) classe par classe.
Quels sont les usecases et les fréquences du besoin d'hériter un constructeur ?
Une approche juste serait peut-etre de combiner un approche KISS des constructeurs (méthode comme les autres) mais saupoudrée de facilité syntaxique pour éviter d'écrire du code idiot et de proposer des solutions aux pb usuels.
On pourra ajouter à la librairie des sockets, une méthode qui pourra spécifier les performances de la socket, comme en java, on a une méthode dans la librairie des sockets qui peut spécifier : le temp de connection la latence et le débit d'une connection socket.
public void setPerformancePreferences(int connectionTime,
int latency,
int bandwidth)
Concernant documentation sur les collections de Nit, nous avons relevé plusieurs points/perspectives d'améliorations :
- Concernant la classe Collection, on ne voit pas les classes filles.
Il serait intéressant de créer des pages destiné aux développeurs pour les guidés dans l'utilisation de tel ou tel collection dans leurs programmes. En effet, nous avons relevé 27 classes utilisables, ce qui fait tout de même beaucoup. Elles permettraient de répondre à des questions tels que : Que dois-je utilisé lorsque je veux trier une collection ? Faire une itération ? Associé une valeur à un clé ?
Christophe GIGAX et Benjamin JOSE
L'idée est de devoir écrire self tout le temps (a la python et smalltalk)
Exemple :
fun tata: X do ...
fun tata=(x:X) do ...
fun test do
var toto: X = ...
...
toto = toto.bloup
tata = tata.bloup # serait refusé
self.tata = self.tata.bloup # OK
if toto isa Y then ... # typage adaptatif
if tata isa Y then ... # serait refusé (actuellement acceptée sans typage adaptatif)
if self.tata isa Y then ... # OK mais pas de typage adaptatf
Avantages :
Inconvénient :
var toto = toto
commence a devenir idomatique mais c'est pas [[KISS]] du tout.
Une solution serait d'interdir ce truc et de forcer le self que pour ce cas la.
var toto = self.toto
Peut-on généraliser à toutes les utilisations de symboles seuls en role d'expressions.
toto.tata # toto must be a local variable?
self.toto.tata # toto must me a function of self
toto().tata # beuh :/
(toto).tata # encore beuh
Il faudrait des stats pour mesurer les occurrence cas.
Concernant la documentation pour la librairie socket:
Aymen Frikha
En ce qui concerne la méthode .ascii des Int et Chars, je serais d'avis de la supprimer et de la remplacer par to_c et to_i respectivement pour plusieurs raisons:
Des avis là-dessus ?
UTF8
J'ai envie de définir des trucs:
Le problème actuel c'est que quand tu veux faire une API propre distribuée sur plusieurs modules t'es obligé d'utiliser des private et des intrude. Or si j'évolue dans le même namespace que mon module, je suis moi même un développeur de l'api, ce qui est défini privé pourrait m'intéresser.
Aussi je proposerais d'élargir la notion de privé à ce qu'on définirait comme un package
L'autre chose qui m'embête c'est la visibilité des redefs En termes de doc ce qui m'intéresse: * si je suis dev de l'api: voir les redefs internes d'un package * si je suis client, voir seulement la somme de ces redefs au niveau du public_module
Mais en terme de doc ça donne quoi? * Si je suis en --private j'affiche toutes les redefs et leurs comments * Si je suis en --public j'affiche seulement l'intro
Mais dans certains cas l'intro ne contient pas de comment et les redefs si, ça serait tentant de prendre la suivante dans la linéarisation des propdefs mais sémantiquement est-ce que ça fait du sens?
Du coup il faudrait aussi se pencher sur une "spec" pour les commentaires de redef. Que doivent-ils expliquer?
J'ai croisé des cas où l'intro n'a pas de comment car le nom de la méthode est obvious. Dans la linéarisation, la propdef suivante défini le comment:
# fast implementation
Afficher seulement le comment de la redef c'est tout pourri dans ce cas.
Autre cas, l'intro à un commentaire mais il s'agit d'un stub. Les redefs viennent compléter le stub du commentaire. Dans ce cas la somme des comments est intéressante voir nécessaire pour bien comprendre de quoi il s'agit.
Pour ce qui est de la gestion de la doc lors des redéfinition, j'en envie de dire que ça dépend et pire, différent morceaux d'un même cartouche devraient pouvoir évoluer indépendamment. Par exemple lors de l'introduction d'une méthode, on document a la fois le service et l'implémentation (complexité algo par exemple). Lors d'une redéfinition, le service est le même, on veut garder ça, mais l'implémentation change, on veut mettre notre propre complexité.
Proposition pour la nitdoc en l'état:
Ces comments sont filtrés en fonction de la visibilité:
Il n'y a pas d'exemple pour cette méthode.
Example:
assert (‘’/home/vincent/Downloads/hello_world.nit’’.basename(‘’.nit’’)) == '’hello_world’’
La méthode équivalente en C# (GetFileNameWithoutExtension(string path) :String) se trouve dans une classe dédié plus adaptée à la gestion des chemins : "System.IO.Path". Il faudrait déplacer cette méthode dans le module "file"
La méthode C# est également plus pratique puisqu'elle retire toute extension qu’elle connaît, du chemin qu’elle a en paramètre. L’utilisateur n’a pas besoin de connaître et de passer l’extension en paramètre. Cela réduit le temps d'implémentation. Par exemple, si l'on souhaite utiliser cette fonction sur des images utilisant différentes extensions (jpg, png, ...). Il faudrait créer une méthode semblable en Nit qui connaîtrait les principales extensions. On utiliserait la méthode existante pour les extensions atypiques.
La méthode équivalente en C# (GetDirectoryName (string path) : string) se trouve dans une classe dédié plus adaptée à la gestion des chemins : System.IO.Path. Il faudrait créer un module ou une classe pour la gestion des dossiers et y déplacer cette méthode.
On parle ici des erreur prévisibles au niveau de l'api des méthodes.
Exemple: un open d'un fichier se fait mal.
On ne parle pas des erreurs plus difficile a rattraper (outofmemory, assert pété, division par 0, etc.)
Donc que doit-on :
Notes: le choix n'est pas nécessairement exclusif ; on peut combiner les approches
code lib:
class File
# write a string, may return an error
fun write(str: String): nullable IOError
# read a line and append it to a buffer, may return an error
fun read_line(buf: Buffer): nullable IOError
end
code client:
var f: File = ...
var line = new Buffer
var error = f.read_line(line)
if error != null then
print "grr: {error}"
else
print "lu: {line}"
end
code lib:
class File
# write a string, modify last_error on error
# no-op if last_error was not null
fun write(str: String)
# read a line, modify last_error on error
# return "" if last_error was not null
fun read_line: String
# The last error that occured on the file
fun last_error: nullable IOError
end
code client:
var f: File = ...
var line = f.read_line
var error = f.last_error
if error != null then
print "grr: {error}"
else
print "lu: {line}"
end
code lib:
class File
# write a string, may return an error
fun write(str: String): nullable IOError
# read a line, may also return an error
fun read_line: (String, nullable IOError)
end
code client:
var f: File = ...
var line, error = f.read_line
if error != null then
print "grr: {error}"
else
print "lu: {line}"
end
code lib:
class File
# write a string, may return an error
fun write(str: String) !on_error(error: IOError)
# read a line, may also return an error
fun read_line: String !on_error(error: IOError)
end
code client:
var f: File = ...
var line = f.read_line !on_error(error) do
print "grr: {error}"
return
end
print "lu: {line}"
code lib:
class File
# write a string, may return an error
fun write(str: String): nullable IOError
# read a line, may instead return an error
fun read_line: String or IOError
end
code client:
var f: File = ...
var line = f.read_line
if line isa IOError then
print "grr: {line}"
else # ici le typage adaptatif determine que dans le else, line isa String
print "lu: {line}"
end
code lib:
class File
# write a string, may return an error
fun write(str: String): nullable IOError
# read a line, may instead return an error
fun read_line: Either[String, IOError]
end
code class Either (lib std):
class Either[F, S]
var is_first: Bool
fun is_second: Bool do return not is_first
var maybe_first: nullable F
var maybe_second: nullable S
init init_first(f: F)
do
is_first = true
maybe_first = f
end
init init_second(s: S)
do
is_first = false
maybe_second = s
end
fun first: F
do
assert is_first
return maybe_first.as(F)
end
fun second: S
do
assert not is_first
return maybe_second.as(S)
end
end
code client:
var f: File = ...
var res = f.read_line
if res.is_second then
print "grr: {res.second}"
else
print "lu: {res.first}"
end
j'aime pas, donc pas d'exemples, na!
pro: Bonne auto-documentation possible
Proposition : fournit plusieurs .sav et plusieurs .fail :
Ne pas réinventer la roue et profiter d'outils existants
Malheureusement, aussi bizarre que ca parraise, il n'y a pas foule de format de sortie standard.
prove
est meme dispo sur zeta)En shell:
En Nit:
En Perl:
L'héritage multiple semble ne pas pouvoir se passer de linéarisation:
super
.Le mieux est C3, toutefois elle est plus adaptée aux langages à typage dynamique car à cause des interfaces, trop de programmes seraient rejetés :
class Comparable
...
end
class Serializable
...
end
class A
super Comparable
super Serializable
end
class B
super Serializable
super Comparable
end
class C
super A
super B
end
C3 ne peut linéariser la class C car in y a un conflit entre Serializable et Comparable.
Or cette incompatibilité est artificielle car l'ordre des super dans A et B est aléatoire
On pense ajouter une clause de fallback à C3 pour préférer les linéarisation des ancêtres prioritaires.
Donc, pour A:A>Comp>Ser>Obj et B:B>Ser>Comp>Obj, C préférera
être montone sur la linéarisation de la première super-classe (A) qui dit que Comp>Ser. Donc on aura C:C>A>B>Comp>Ser>Obj.
Toutefois la clause de fallback semble difficile à coder dans le cas où on ait des types intersection ou de la [[sous-spécialisation de classes génériques]].
Dois-on résoudre automatiquement les conflits de valeur ou demander un truc explicite au programmeur ?
Doit-on accepter le programme suivant ?
class A
fun toto ...
end
class B
super A
redef fun toto ...
end
class C
super A
redef fun toto ...
end
class D
super A
super B
end
var a: A = new D
a.toto
Doit-on lever une erreur et demander explicitement quelque chose comme
class D
...
redef fun toto do super
La méthode équivalente en C# (GetEnvironmentVariable (string variable) : string) se trouve dans une classe dédié plus adaptée à la gestion des variables d’environnement : System.Environment. Il faudrait déplacer cette méthode dans le module "environ".
Lorsque je test cette méthode, elle renvoie void. Retourner une exception ou un message à la place du void et une documentation plus précise et un exemple aideraient à une meilleure compréhension de cette méthode.
Il faut préciser des règles de formatage de l'autodoc qui soient utilisables par les humains et les machines.
La base serait de partir d'un markdown avec une analyse syntaxique et semantique du code (entre back-quotes ou en block)
Exemple:
# Create a substring from `self` beginning at the `from` position
#
# assert "abcd".substring_from(1) == "bcd"
# assert "abcd".substring_from(-1) == "abcd"
# assert "abcd".substring_from(2) == "cd"
fun substring_from(from: Int): String do ...
To define an annotation on a class:
class Foo annotation
...
end
To define an annotation on a property:
fun foo is annotation
Why there is no need of the "is" keyword for classes annotations?
class Foo is annotation
Better looking if combined with an 'abstract' annotation:
class Foo is abstract, annotation
Is there a SableCC grammar impossibility or something?
La méthode utilise le paramètre entre crochets, et non pas à côté des crochets:
Il n'y a pas de documentation et d'exemple pour cette redéfinition de méthode.
Doc redefine:
return the character at the index in the string.
Example:
assert (‘’bonjour’’[3]) == j
La méthode équivalente en C# (SetCurrentDirectory (string path) : void) se trouve dans une classe dédié plus adaptée à la gestion des Dossiers : System.IO.Directory. Il faudrait créer un module ou une classe pour la gestion des dossiers et y déplacer cette méthode.
L'idée est de pouvoir spécialiser une classe générique en modifiant les bornes.
Exemple:
class Array[E: nullable Object]
...
end
redef class Array[E: Comparable]
fun sort ... # Sort n'est dispo que pour les tableaux de comparable ou leur sous-types)
end
On voudrait avoir un maximum de latitude :
l'interdiction des interdépendance est génante
Propositions:
Un namespace (nom temporaire) permet de déambiguiser un nom de classe ou un nom de propriété.
Un namespace est unique dans l'univers.
les collisions sont traités à un autre niveau que celui du langage (variable d'env, option du compilateurs etc.)
Dans un namespace les noms publiques son uniques.
ex:
mynameespace::MyClass
-> l'unique classe publique MyClass du namespace mynamspace
mynameespace::MyClass::myprop
-> l'unique propriété myprop
du namespace mynamspace
; la prop est introduite dans une classe MyClass
Un namespace est composé d'identifiants séparés par des :: formant un pseudo-hiérarchie
Par pseudo-hiérarchie, j'entends qu il n'y a pas de sémantique Nit associés aux hiérarchies.
Ex. de namespaces:
nit
(serait réservé pour les trucs standards)opts
pour la lib d'optionsThere is a typing error with varargs in nitg.
Exemple:
class A
fun variadic(b: Discrete...) do
...
end
end
class B
super A
redef fun variadic(b: Discrete...)
do
super
end
end
Here, the variadic method expects a vararg Discrete..., the same type in A and B and it's works.
But if I change the class B for
class B
super A
redef fun variadic(b: Discrete...)
do
super(b)
end
end
I get the compilation error:
Type error: expected Discrete, got Array[Discrete]
super(b)
Some module are duplicated in the list and also in the graph.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.