Coder Social home page Coder Social logo

nitlang / nit Goto Github PK

View Code? Open in Web Editor NEW
236.0 236.0 64.0 121.41 MB

Nit language

Home Page: http://nitlanguage.org

License: Apache License 2.0

Makefile 0.32% Shell 0.32% Nit 21.78% Java 0.08% C 76.27% Objective-C 0.01% Brainfuck 0.01% Python 0.01% Perl 0.05% HTML 0.17% CSS 0.63% JavaScript 0.19% Haskell 0.01% Ruby 0.01% Go 0.01% R 0.01% TeX 0.01% Pep8 0.17% Smarty 0.01% Roff 0.01%
compiler language nit

nit's People

Contributors

ablondin avatar alexandre-pennetier avatar anisboubaker avatar blackminou avatar calestar avatar captainkali avatar delja avatar djomanix avatar dullin avatar freddrickk avatar geoffreyhecht avatar itswill avatar jcbrinfo avatar jpages avatar julienchevalier avatar kugelbltz avatar lbajolet avatar lucasmatthieu avatar lvboudre avatar matthieuauger avatar mehdiait avatar morriar avatar nheu avatar patrickblanchette avatar ppepos avatar privat avatar renatamc avatar stefanlage avatar tagachi avatar xymus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

nit's Issues

Online NitDoc / Class standard::String / Method ==

La méthode "==" apparaît deux fois dans la liste Methods de la sidebar Properties mais référence la même méthode:
methode

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

bug: Native objects not garbage-collected

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)

spec: Fermetures

Problème des fermetures actuelles

  • syntaxe pourrie
  • intérêt des fermetures "en avant" (aka "sur la pile") pas clairement démontré

Proposition

idée 1, une fermeture est un argument comme les autres:

  • dans les parenthèses
  • matching positionnel et non plus sur le nom

idée 1bis:

  • les parenthèses sont optionnelles

idée 2, on cherche sans mot clé:

  • xxx est le mot clé de démarcation de la fermeture (actuellement "!" + identifiant)
  • yyy est le mot clé du retour de la fermeture (actuellement "continue")

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

Fermetures normales (lambdas)

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:

  • pour les fermetures en avant, xxx = for
  • pour les fermeture normales, xxx = 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)

spec: Paramètres par défaut

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

Avantages

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.

Inconvénients

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

todo: Printing nullables

Print a nullable var

print x

and

print "{x}"

Throw a compile error if x is nullable.

Solutions

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

spec: AST ou icode ?

Quelle représentation utiliser lors des différentes phases de compilation ?

État des lieux:

  • l'AST de Nit est plus élégant et robuste
  • deux module de debug, astprinter et astvalidation commencent a être développés
  • un module utilitaire, astbuilder d’instanciation et transformation
  • une phase transform transforme une partie des noeuds compliqués en des noeuds plus simpls

Utiliser l'AST

Avantages:

  • une seule représentation a comprendre et a utiliser
  • on peut générer du code à partir de chaines

Inconvénients:

  • un paquet monstrueux de classes
  • instances faciles à rendre incohérentes

Utiliser une représentation intermédiaire

Avantages:

  • modèle plus simple

Inconvénients:

  • A un moment il faut switcher de représentation

Un truc hybride

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.

todo: Suppression de l'ancien métamodèle

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 garde le vieux

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 garde le nouveau

on passe le compilo séparé actuel en déprécié.
Ça veut dire qu'il faut :

  • finir le nouveau métamodèle
    • métrique: passer le tests de régression
    • interface native
    • linéarisation
    • fermetures
  • implémenter les solutions aux bloqueurs de [[v1.0]] dans le nouveau métamodèle
  • implémenter un compilateur séparé et la nitdoc avec le nouveau métamodèle
    • nouvel icode

todo: Covariance vs. varargs

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:

  • On veux des V;
  • statiquement mon receveur est un A et on peut vérifier que c'est des des X qu'on passe (mais c'est pas suffisant, la sureté n'est pas garantie);
  • dynamiquement le receveur est un B donc on veux des Y, et on passe effectivement un Y. On devait etre content

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

Discussion

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

Instantiation dynamique

Au lieu de faire varargs = new Array[X], faire varargs = new Array[a.V]

Problèmes:

  • type formel sorti: cela serait la première fois qu'un type formel puisse sortir du receveur courrant.
  • test de covariance sorti: en cas de vrai erreur de covariance, l'erreur serait soulevé dans le varargs.add implicite bien avant l'appel à foo (c'est pas très grave mais c'est incohérent avec la covariance sans varargs)

Encapsulation dans une méthode polymorphe

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

  • ca ne marche pas avec les types paramétriques formels (du moins sans hétérogénéité)
  • test de covariance sorti (coir dessus)

Entrer le new dans le foo

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:

  • c'est quoi ce machin magique?

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.

Online NitDoc / Class standard::String / Method file_delete

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.

todo: Nouveau Métamodèle

Hiérarchie de mclass

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

MClass <-> MClassType

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.

POSetElement::greaters

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)

Vue globale vs. vue par module

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.

Misc.

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.

spec: Sémantique des attributs non initialisés

Grace aux nullables, un attribut non nullable possède une sémantique claire :

  • tant que celui-ci n'est pas initialisé, y accéder avorte le programme
  • une fois celui-ci initialisé, il n'y a plus de problème.

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 :

  • dans le cas nullable initialisé par défaut à null ;
  • ou dans le cas non nullable et non initialisé.

En fait c'est pas si évident (sinon il y aurait pas cette page)

Cas des types formels

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 ?

Cas des types primitifs

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.

TP INF7845: Socket API: Timeout

  • Utilisation d’un timeout dans la méthode connect ( qui spécifie la durée pour laquelle le client doit attendre pour l'établissement d’une connection), présente dans JAVA.

Exemple:
stream_with_host(thost: String, tport: Int, timeout:Int)

todo: Typage adaptatif

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)

spec: abort

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.

Commentaire de Jean

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

  • nbre de abort: 378
  • dont précédés d'un print: 18
  • ou précédés d'un debug: 4 (pour message mieux formatés)
  • nbre de assert: 2076
  • dont incluant un else: 12
  • dont en realite juste "else print": 4

todo: String vs Collection[char]

Est-il légitime de généraliser string en une collection[char](voir sequence[char]) ?

Qu'est-ce qu'on choppe de ces classes?

méthodes de Collection et Sequence utiles

  • length
  • is_empty
  • []
  • first
  • last

méthode de Collection et Sequence pas si utile ou trompeuses

  • count
  • has
  • has_only
  • iterate/iterator
  • join
  • to_s
  • index_of

methode de pipeline utiles

  • (concaténation paresseuse)
    head
    skip_head
    skip_tail
    tail

methodes de pipeline pas si utiles ou trompeuses

skip
alternate
sort_filter
sort_with
uniq
seq_uniq

Et si String n'était pas une collection?

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é

Autre idée

L'idée, pondue a 2h du mat, est de fusionner [[!nitdoc Collection]] et [[!nitdoc SequenceRead]].

Pro

  • une interface de moins dans la hiérarchie
  • la sémantique du for (et du iterate) est déterministe et simplifiée
  • le comportement des modification de collection concurrentes a des itérations peut enfin être précisé

Con

  • aucun, si ce n'est p'tet un manque d'intuitivité. mais franchement qui utilise SequenceRead ? 0 occurrence dans lib/, src/ et exemples/ sauf pour déclarer la classe et les deux sous-classes.

Mise en oeuvre

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.

spec: Nouveaux nouveaux attributs

L'objectif est d'améliorer la syntaxe des attributs dits « nouveaux-style » sans toucher à la sémantique.

TL;DR

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

Rappel

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.

Proposition d'Alexandre

  • les mots clés readable et writable sont remplacés par getter et setter.
  • la visibilité par défaut des attributs est:
    • inaccessible pour les attributs
    • privé pour le getter (et non plus public par défaut)
    • privé pour le setter
  • les mots clés getter et setter suivent la déclaration et sont précédés par une virgule.

Exemples

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

Visibilité et redef

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

Multiligne

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

getter et setter nommés

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

Getters et setters multiples

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

Documentation

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

Future-proof

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

spec: Choses primitives de la lib

Idéalement, on voudrait un maximum d’indépendance entre :

  • la spécification du langage Nit
  • le compilateur, qui doit seulement être subordonné à la spec
  • la bibliothèque standard, qui ne devrait être qu'une bibliothèque Nit comme les autres

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#+

Fonctions dont l'implémentation est connue

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

Détail d’implémentation dans la lib

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.

Opacité et Fragilité du NativeArray

Par opaque, chaque moteur d'exécution fait comme il veut.
Actuellement, on a:

  • Compilo séparé : c'est en fait une structure ad-hoc 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).
  • PRM original : un NativeArray n'était qu'un val_t*. On a ajouté une entete devant uniquement pour que le GC puisse visiter les objets.
  • Compilo global : un NativeArray est tableau C primitif customizé (donc Array[Int] -> int*) cette fois ya vraiment rien, meme pas d'info sur la taille
  • Interpréteur : une instance de NativeArray est représenté par un PrimitiveInstance[Array[Instance]]. ça a l'air chaud, mais ca veut juste dire qu'un objet NativeArray dans le programme est représenté par un objet PrimitiveInstance dans l'interpréteur et que les éléments du NativeArray sont représentés par un Array[Instance]

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

Instanciation de Array

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

Accès aux éléments

Array est une classe très utilisée en Nit:

  • Elle possède une forme littérale.
  • Elle sert aux varargs
  • Elle est utilisée pour l'implémentation de plein d'autres classes de la lib standard

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 :

  • dispatch de Sequence::[] sur a. (le [] de Array est introduit dans Sequence, une sous-classe de Collection)
  • empilement de contexte et exécution de Array#[]
  • vérification que l'index 1 est valide dans pour a
  • dispatch de NativeArray::[] sur a.native
  • empilement du contexte et exécution de 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.

Itérer un Array

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) :

  • PRM regardait si statiquement la collection du for est statiquement un Array. si c'est le cas la version efficace est utilisée (indépendamment du code de la lib standard)
  • le compilateur séparé Nit est un peu plus propre: Array#iterate qui contient en fait la version efficace est simplement inliné
  • l'interptéteur et le compilateur global utilisent la version par itération.

Propositions pour améliorer les choses

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

bug: formal types in class refinements can be named as real types

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

todo: Quelle interface native ?

Actuellement il y a 3 (environ) specification/implémentation d'interface native.

  • le système ad-hoc original legacy
  • l'interface native de la maîtrise d'Alexis (nitni et nits)
  • ffi de la thèse d’Alexis (work in progress)

Question: qu'est-ce que qu'on veut pour la [[v1.0]] ?

Idéalement, il faudrait :

  • en choisir un seul
  • qu'il soit efficace
  • qu'il soit robuste
  • qu'il soit portable sur les 3 moteurs (nitc, nitg, niti)
  • qu'au moins une interface soit rapide à implémenter sur un nouvel engin
  • que l'utilisation de l'interface avancée soit possible à ignorer

Proposition

  • Utiliser la FFI pour les utilisations avancées
    • Forme très efficace
    • Peut être failement ignoré par un engin limité
  • Utiliser l'interface legacy modifiée pour les fonctionnalités de base à la limite d'être intern
    • Robuste et rapide à implémenter

Modifications à apporter à l'interface legacy

  • Y interdire tout rappel à Nit et l'utilisation de types Nit non-primitifs
  • Ne plus utiliser de macros pour implémenter les méthodes externes
  • Utiliser la forme de nitni pour identifier les fonctions d'implémentations
  • Abandonner la spécification manuelle du nom en C?
    • Généralement superflue mais peut permettre d'éviter des conflits
    • Au besoin, déplacer cette fonctionnalité dans la FFI

Online NitDoc / Class standard::String / Method compare_to

La méthode "chaine1.CompareTo(chaine2) : Int" de C# permet de faire le travail de comparaison lexicographique des deux de NIT : "<" et "==".

  • 0 : les deux chaînes sont identiques,
  • < 0 : la première chaîne vient avant la deuxième,
  • 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.

spec: Constructeurs

Les constructeurs c'est horrible :

  • ca pause plein de problèmes
  • c'est fondamental dans le langage
  • beaucoup d'attendes différentes en fonction des programmeurs et des langages qu'ils connaissent (bonjour le principe de moindre surprise)

Les constructeurs en héritage multiple sont problématiques à plusieurs niveaux :

  • chaînage obligatoire (donc exacerbation des pbs de linex ou d'invocation répétée)
  • fragilité du l'encapsulation (fragile-base-class)
  • fragilité des contrats : invariant de classe non validé
  • double rôle des constructeurs : new et super-init qui se combine mal

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.

Approches existantes

Eiffel/Ruby

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.

C++

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)

Java/C

Spécialisation simple, les chanceux.

Besoin et pb des constructeurs

  • initialiser les attributs (avec du code plus ou mois complexe)
  • effecteur certain appels pour modifier l'état du programme (effets de bords). Exemple enregistrer l'objet dans une structure obtenue via un paramètre ou via un objet global.

Le problème est principalement:

  • la possibilité d'avoir plusieurs constructeurs.
  • les paramètres des constructeurs.

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)

Points d'accroche

Appel implicite des super-constructeurs

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.

Initialisation automatique des attributs via les arguments du constructeur

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.

Héritage des constructeurs

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 ?

Tentatives de propositions pour Nit

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.

TP INF7845: Socket API: Socket performance

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)

TP INF7845 Online NitDoc / Collection Library

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

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

spec: Rendre self explicite

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 :

  • l'acces a une variable locale est visuellement différent d'un appel a un getter ou un setter sur self.

Inconvénient :

  • loudingue ?

Autre commentaires sur le cas actuel

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.

TP INF7845: Socket API

Concernant la documentation pour la librairie socket:

  • Aucune introduction aux principes des sockets
  • Les classes et les méthodes ne sont ni commentées ni expliquées
  • Le naming des classes n'est pas explicite: On ne comprend pas la différence entre la classe FFsocket et socket.
    Concernant l'API socket:
  • Pas de support de l'IPv6
  • On ne peut pas spécifier le type de la socket : TCP/UDP (En java on peut le spécifier dans le constructeur de la classe)
  • On pourra ajouter la méthode write_to(thost : String , msg: String) qui nous permettra d'envoyer des messages à la socket distante sans être connecté à elle au préalable.

Aymen Frikha

Méthode .ascii

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:

  1. Le support de l'ASCII étant plus ou moins en sursis au profit d'un encodage Unicode, la méthode ascii n'aura plus de raison d'exister. Est-ce que ça ne vaudrait pas le coup de le supprimer directement afin d'avoir la voie libre par la suite, l'implémentation devra être modifiée, mais au moins le comportement entre deux versions serait cohérent.
  2. La méthode .ascii existe dans Char et Int et est définie deux fois, est-ce qu'il ne serait pas mieux de remplacer les to_c et to_i actuels par ce comportement ? De cette façon, à mon sens, c'est plus logique comme ça. Sémantiquement, on aurait un comportement logique peu importe l'encodage qu'on supportera par la suite. Qui plus est, la spécification actuelle de to_c sur les Int est assez étrange à mon sens, ne vaut-il pas mieux renvoyer le char ASCII pour le moment, quitte à renvoyer une erreur explicite quand on a un code invalide ? On aurait juste à changer les bornes de l'erreur après ça pour supporter le nouvel encodage quand il arrivera.

Des avis là-dessus ?

todo: Ropes

Ropes

  • finir ropes équilibrés
  • finir modèle et vues

refactor Strings

  • déplacer les appels aux constructeurs de String vers des services de NativeString => ffi et compilos
  • changer modèle : Text superclasse, Flat/Buffer(== Mutable)/String (== Immutable)/Rope subclasses de Text, FlatString subclass de String et Flat, FlatBuffer subclass de Buffer et Flat, BufferRope subclass de Buffer et Rope, RopeString subclass de Rope et String
  • ajouter un service chars pour une vue vers un *Sequence[Char]
  • remplacer les utilisation de string en tant que séquences
  • détacher String de Sequence[Char]

Ropes encore

  • faire de ropes un sous-classes de String
  • faire des ropes sans equilibrage
  • faire des ropes avec equilibrage lazy
  • faire avec des B+trees
  • faire avec des peignes (simulation du Array[String])

Encore plus

UTF8

spec: Visibilité, raffinements et commentaires

J'ai envie de définir des trucs:

  • privé à un module
  • privé à un package
  • protected
  • public

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:

  • si tu es sur la doc l'intro tu as juste le doc d'intro
  • si tu es sur la doc d'une redef tu as tout les comments vers le haut depuis le comment de la redef

Ces comments sont filtrés en fonction de la visibilité:

  • public tu vois seulement les comments d'intro ou de spécialisations + les comments de prop redéfinis dans des public_modules différents du public_module d'intro
  • private: tu vois aussi les comments des redefs dans les nested

todo: FFI

  • GC des objets primitifs et natifs : en particulier pouvoir ramasser des chaines (cf. http://nitlanguage.org/bugs/Native_objects_not_garbage-collected)
  • Blinder le comportement des fonctions systeme actuels (file, process, etc.). Au pire aborter plutot que continuer mal.
  • Interface native pour le nouveau modele
  • Interface native pour l'interpréteur
  • Publier la bibliothques SDL/OpenGL,/etc avec un exemple pour que les gens commencent a développer des jeux simples (linux-olny, pas forcément en cross compilation android)
  • Publier la bilbiothèque réseau

Online NitDoc / Class standard::String / Method basename

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.

Online NitDoc / Class standard::String / Method dirname

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.

todo: Gestion des erreurs

Gestion des erreurs

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 :

  • fournir pour la gestion des erreurs dans les lib standard de nit
  • conseiller comme bonne pratique dans la doc de nit

Notes: le choix n'est pas nécessairement exclusif ; on peut combiner les approches

Etat de l'art

cas d'erreur indiqué dans le retour normal (a la C)

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
  • pro: c'est simple si on a à faire avec un procédure
  • pro: pas besoin de modifier le langage
  • con: c'est chiant si le truc est déjà une fonction

cas d'erreur indiqué dans l'objet receveur (a la C++)

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
  • pro: objet
  • pro: pas besoin de modifier le langage
  • con: on a pas toujours un objet pertinent où stocker les erreurs éventuelles

retour multiples, l'erreur est l'un des retours (à la go)

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
  • pro: clean
  • pro: on force un peu le client a prendre l'erreur
  • con: faut implémenter les retours multiples

utilisation de fermetures en avant

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}"
  • pro: classe : pas besoin de if
  • pro: pas besoin de modifier le langage
  • con: les [[fermetures]] de nit sont pas top (voir page dédiée)

retour de types union

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
  • pro: magique
  • con: types union pose des pb sémantiques
  • con: marche pas si le retour est un truc du genre "Object or PloupError" car les types ne sont pas disjoints

retour de types "alternative" (à la haskell)

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
  • pro: pas besoin de modifier le langage
  • con: franchement, c'est sérieux comme proposition?

exceptions (a la java)

j'aime pas, donc pas d'exemples, na!

pro: Bonne auto-documentation possible

todo: Tests de regressions

Compatibilité avec plusieurs moteurs

Proposition : fournit plusieurs .sav et plusieurs .fail :

  • le test est ignoré s'il n'y a ni sav ni fail associé
  • sinon, le test passe s'il est correspond à un sav
  • sinon, le test passe en erreur attentue s'il correspond un fail
  • sinon, le test echoue

Format de sortie standard

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.

TAP

  • vieux standard (1987)
  • pas mal portable (codé en perl, la commande prove est meme dispo sur zeta)
  • utilisé par de vrais projets (comme Git)
  • beaucoup de lib dans de nombreux langages
  • utilisé par plusieurs frameworks
  • plugin pour Jenkins

Subunit

  • plus moderne que TAP
  • moins portable (python)

format de jenkins/hudson pour junit

  • XML :(
  • pas d'outil

Recoder le chose

En shell:

  • c'est lound a lire/ecrire et peu portable (sed, etc)

En Nit:

  • ca nécessite de compiler le programme pouvoir lancer les tests
  • c'est possiblement plus lent (temps de compilation)
  • c'est dangereux (si le compilo marche mal)
  • le lib standard de Nit et peu robuste pour les trucs systèmes

En Perl:

  • très bonne portabilité
  • Perl a l'air suffisant en terme de fonctionnalité pour ce genre de travail

Fonctionnalités

  • parallélisation des tests
  • report automatique (html, jenkins, etc.)
  • intégration de bigtests pour tester plus que que le compilateur (bootstrap, doc, etc.)
  • suites de tests granulaires, on ne veut pas relancer les 100000 tests à chaque fois qu'on fait une modification sur les closures par exemple.

todo: Linéarisation

L'héritage multiple semble ne pas pouvoir se passer de linéarisation:

  • ordre d'invocation des initialisation des attributs
  • appel à super.
  • héritage dans les conflits de valeur.

Quelle linéarisation choisir?

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

Conflit de valeur

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

Online NitDoc / Class standard::String / Method environ

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.

spec: Formatage d'autodoc

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

syntax: Annotations

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?

Online NitDoc / Class standard::String / Method []

La méthode utilise le paramètre entre crochets, et non pas à côté des crochets:

  • sur la documentation : [](index: Int): E
  • utilisation réel : [index: Int]: E
    Il faudrait modifier la documentation pour que l'en tête de méthode corresponde à l'utilisation réel.

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

Online NitDoc / Class standard::String / Method chdir

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.

todo: Sous-specialisation de classes génériques

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 :

  • ajout de nouvelles propriétés (méthode, attributs, constructeurs, types virtuels)
  • redéfinition de méthodes
  • ajout de super-classes

spec: Modules, imports et espace de nom

Trouver le module top-level

  • Modifier le comportement de la résolution d'imports pour qu'elle monte jusqu'au root au lieu de s'arrêter au dossier en cours de compilation. -> idée ignorée

autoriser les imports circulaires

l'interdiction des interdépendance est génante

espace de noms.

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'options

bug: Typing with varargs

There 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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.