Acid, le lisp-like de la communauté !

Créons notre langage de programmation ! Pour le fun !

a marqué ce sujet comme résolu.

D'accord donc il faut que j'écrive un typechecker statique qui vérifie la cohérence des types de mon AST Acid. Du coup il va falloir coder un inférenceur de type non ?

AlphaZeta

Je passe en coup de vent pour dire que non : la spec dit bien que toutes les lambdas doivent avoir une contrainte de type (un bloc hastype), justement pour qu’il n’y ait aucune ambiguïté, et qu’on n’ait pas besoin d’inférence de types. :)

Dominus Carnufex

Ne vous focalisez pas trop sur la spec : elle est de toute façon elle-même ambiguë (surtout sur la partie typage qui est un peu fumeuse - la faute sans doute à un manque d'habitude sur la formalisation de ce genre de chose), et comme dit plus haut, elle ne doit sûrement pas être vue comme une limitation.

Pour répondre à ce point précis : tel que c'est écrit, et si on interprète ça raisonnablement, il faudra de toute façon écrire un typeur qui fasse un peu plus que juste vérifier que les appels de fonctions sont corrects. Par exemple, si tu écris (hastype if (a -> b -> b)), le typeur doit t'envoyer bouler (sans mauvais jeu de mot) parce que le type est trop générique (a ne peut être que bool). Ça demande un minimum d'inférence.

La conclusion n'est pas qu'il faut réécrire des lignes et des lignes pour essayer de "spécifier" quelque chose de formel sans utiliser les outils adaptés, mais plutôt qu'il faut se donner plus de souplesse. En l'occurrence, si vous voulez jouer avec le typage, prenez un système qui existe déjà (par exemple ML, qui est plus ou moins ce qu'a essayé de faire celui qui a écrit la spécification) et lisez ce qui a été écrit dessus. Par ailleurs, quand vous aurez un peu plus d'expérience, vous pourrez chercher à concevoir vos propres systèmes - mais clairement, pour l'instant c'est beaucoup trop tôt.

+3 -1

Salut,

J'ai de nouveau accès à ce que j'avais implémenté du système de typage. Du coup j'ai pas encore intégré ça aux modifications que j'ai fait entre temps mais je push déjà le REPL. Vous pouvez le télécharger ici.

Je me suis un peu appliqué pour le REPL, du coup j'ai fait un interpréteur style GHCi avec des commandes et tout.

Par contre j'ai pas encore touché à colorama.

AZ.

PS: Des petits screens pour ceux qui ont la flemme de télécharger:

Comme vous pouvez le voir il reste des petits problèmes. Surtout cet ASCII art qui est absolument moisi :P

+8 -0

Question pour les pythonnistes encore: Comment je fais pour attraper une exception KeyboardInterrupt ? J'ai vu que sur certains ordis, faire un except ne marchait pas, donc j'ai bidouillé ça avec les signaux du module signal mais ma boucle du REPL tourne un dernier tour avant de quitter complètement. Quelqu'un a une idée ?

Après ça je pense faire une PR de la branche repl, je reprendrais typing après le bac :)

except KeyboardInterrupt. Si ça ne fonctionne pas le seul cas que je vois c'est que tu essayes de rattraper cette exception dans le mauvais thread, mais je ne crois pas que ton code soit multithread, si ?

+0 -0

(Désolé du double post, mais ça me paraît plus adapté)

Je viens de me rendre compte qu'on ne peut pas faire de fonction if sans évaluer les deux branches (le if et le else). Du coup pour définir une fonction factorielle par exemple c'est pas commode. La solution ici est d'implémenter un système d'évaluation partielle. Je pense m'attaquer à ça avant le typage parce que, une fois que j'ai défini un type Value, tout sera plus simple pour implémenter le typing après.

L'idée c'est qu'on aura un type Thunk qui représentera une expression non évaluée, pour faire du call-by-need plutôt que du call-by-value/reference (stratégie d'évaluation par défaut de Python).

Pour avoir des exemples plus évolués il faudrait déjà pouvoir faire un if à mon avis. Et sans typage on est assez loin de ce que devait être Acid au départ. Si tu tiens à faire une release, attends au moins d'avoir un système d'évaluation paresseuse. On codera le if avec le filtrage par motif quand on aura du typage.

D’une, le if est déjà codé (fichier Prelude.acid de mon code d’exemple. De deux, j’ignore comment tu gères cela, mais comment se fait-il que tu évalues toutes les branches de sortie possible d’un match ? C’est absurde !

Y’a pas besoin d’évaluation paresseuse, juste de respecter la logique de l’instruction. En gros (j’invente, hein), tu aurais une fonction match(valeur, motif) dans ton interpréteur, que renverrait un booléen, et chaque branche du filtrage deviendrait (else) if match(valeur, motif_n): branche_n

Sinon, je suis d’accord que ça me paraît un peu tôt pour faire une release de l’interpréteur. :)

+0 -0

j’ignore comment tu gères cela, mais comment se fait-il que tu évalues toutes les branches de sortie possible d’un match ? C’est absurde !

Justement je n'ai pas encore de pattern matching. C'est parce que je transcris mon AST Acid en AST Python. Or, comme en Acid le corps d'une fonction est une expression, ma fonction if est définie comme ça: 'if': lambda cond, cons, alt: cons if cond else alt. Or, comme cette fonction est une fonction Python, tous ses arguments sont évalués à l'appel.

PS: Bien entendu, à terme, la fonction if ne sera qu'une fonction ordinaire implémentée en Acid grâce au filtrage par motif.

+0 -0

Tu soulèves un point intéressant. La version implémentée directement en Acid ressemble à cela.

1
2
3
4
5
6
(define if (lambda (cond if_true if_false) (
    match cond (
        (Bool::True   if_true)
        (Bool::False if_false)
    )
)))

Et comme tu le fais justement remarquer, si tous les paramètres sont évalués avant même l’entrée dans la lambda, on a un problème. En particulier si ces paramètres contiennent des fonctions avec effets de bord. Genre ça…

1
2
3
(if (< x 0) (print "J’ai dit un nombre positif, tête de mule !")
    (print "T’es un bon petit…")
)

Du coup, il faut l’édicter clairement : les paramètres d’une lambda ne doivent être évalués que lors de leur première utilisation effective dans le corps de la lambda. Ça n’en fait pas de l’évaluation paresseuse pour autant, cela dit, mais c’est important de le noter. :)

+2 -1

Je pense que c'est un peu contre productif de rester sur de la traduction vers l'AST Python parce que si je veux implémenter ce comportement dans Acid, je vais devoir wrapper toutes mes valeurs dans mon type Value et Thunk et créer un type Environnement pour gérer tout ça. Au final ça revient à compiler son code soi même quasiment. C'est pourquoi je pense qu'on peu commencer à aborder LLVM ou n'importe quel autre outil du genre (pas trop bas niveau quand même).

Edit: Sinon personne sait pourquoi je suis toujours pas marqué comme contributeur sur GitHub ?

+0 -0

les paramètres d’une lambda ne doivent être évalués que lors de leur première utilisation effective dans le corps de la lambda. Ça n’en fait pas de l’évaluation paresseuse pour autant

C'est la définition même de l'évaluation paresseuse :-° Mais manifestement c'est juste la définition du if qui vous gêne pour l'instant. Vous n'avez pas besoin de vous embêter avec l'évaluation paresseuse pour ça : ajoutez le comme une construction native du langage avec une sémantique paresseuse, et voilà. Je ne pense pas que ce soit une bonne idée de choisir une évaluation paresseuse par défaut juste pour ce problème : sans bien en comprendre le fonctionnement et les enjeux, vous risquez de vous casser la figure, et c'est par ailleurs à mon avis un mauvais choix d'avoir ça par défaut dans un langage. Ça ne vous empêchera pas d'essayer plus tard, mais c'est prématuré.

Pour le match, c'est à mon avis quelque chose que vous feriez mieux d'enlever : vous pourrez rajouter des types algébriques (et même des types tout court) au langage plus tard, une fois que vous aurez une bonne base et que vous comprendrez ce que vous faites. Pour l'instant, aucune de ces deux conditions ne sont réunies : vous avez déjà dû mal à avoir des conditions dans le langage, et le système de types des spécifications est bancal (ce n'est pas un reproche : c'est dur à concevoir et ça demande de manipuler des concepts particuliers que vous ne connaissez pas encore). Chaque chose en son temps.

Pour la release, vraiment, ne perdez pas de temps avec ça. Personne ne va utiliser zlang de toute façon, ne vous imposez pas un travail inutile juste pour essayer de rentrer dans le moule des méthodes de développement industrielles.

En fait je viens de penser à un truc, pour simuler l'évaluation paresseuse je peux juste wrapper mes valeurs dans une lambda sans argument. Ainsi, si jamais on a besoin d'évaluer foo, on fera foo(). Le seul souci c'est que ça sera évalué plusieurs fois si on en a besoin à d'autres endroits du code…

Sinon Eusèbe, en quoi le système de type de la spec est-il bancal ? (bancal au sens incohérent, pas au sens "pas comme ML" :p )

Edit: Je vais quand même créer un type Value, il sera équivalent à une lambda sans argument mais c'est juste histoire que ça soit plus clair dans le code.

Edit 2: Et aussi, même en implémentant if comme construction par défaut du langage, je pense que c'est impossible de ne pas évaluer les deux branches :(

+0 -0

Supposons cette lambda.

1
2
3
4
5
6
(define uppercase (lambda str (
    match str (
        (List::Nil List::Nil)
        ((List::Cons char next) (List::Cons (uppercase_on_char char) (uppercase next)))
    )
)))

Avec des paramètres évalués lors de leur première utilisation dans le corps de la lambda, (uppercase "abc") renvoie "ABC". Avec de l’évaluation paresseuse, elle renvoie List::Cons [Thunk : (uppercase_on_char 'a')] [Thunk : (uppercase "bc")], et c’est tout. Le contenu des thunks ne sera évalué que si la valeur retournée se retrouve entre les mains d’un filtrage par motif qui fait quelque chose du contenu de Cons.

Par exemple, si le résultat est passé à une lambda length, avec de l’évaluation paresseuse, les thunks utilisant uppercase_on_char ne seront jamais évalués, alors qu’avec ce que je décris, ils le seront.

Après, tu fais comme tu veux, hein… Je voudrais pas te priver de ta dose quotidienne de complexe de supériorité…

+0 -1

Edit 2: Et aussi, même en implémentant if comme construction par défaut du langage, je pense que c'est impossible de ne pas évaluer les deux branches :(

AlphaZeta

Si, en générant une instruction if dans le bytecode Python.

Edit : J'ai dit bytecode, mais je pensais à générer les bons nœuds dans l'AST, comme tu le fais actuellement.

Dans l'évaluation paresseuse on a juste besoin d'évaluer lors du pattern matching du coup ? Maintenant que j'y pense c'est vrai que c'est logique, mais ça veut dire que je vais devoir bosser sur les fonctions primitives. Je verrai ça après le bac encore une fois (ou si quelqu'un veut prendre la relève ça serait bien aussi).

Je voudrais pas te priver de ta dose quotidienne de complexe de supériorité…

Tu parles de qui ?

Si, en générant une instruction if dans le bytecode Python.

entwanne

Euh on fait ça comment :euh:

Comme ça je dirai qu'on pourrait injecter du bytecode avec le module dis en bidouillant avec POP_JUMP_IF_TRUE ou POP_JUMP_IF_FALSE mais concrètement je saurai pas faire. Je vais me renseigner.

Mais si on commence à jouer avec le bytecode directement, autant faire ça pour tous les nodes, et pas juste avec le if. Je propose donc de compiler sans passer par l'AST Python.

Euh on fait ça comment :euh:

En générant l'AST équivalent. Tout simplement.

Ne touche surtout pas explicitement au bytecode lui-même : tu n'en as pas besoin et le code résultant ne sera même pas portable entre deux versions mineures de Python.

+1 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte