Un petit langage ZdS

Amusons-nous !

a marqué ce sujet comme résolu.

Reprise du dernier message de la page précédente

D'abord un langage sans typage n'existe pas, ou alors il manipule toujours strictement le même type de données, ce qui le rend plutôt inutile.

nohar

A moins de travailler sur une version étendue, Prolog est non-typé.

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+0 -0

Python possède un système de typage dynamique donc on à pas besoin d'indiquer le type avant une variable (je pensais que vous le saviez)

Near

Comme dit plus haut, tu peux indiquer les types depuis 3.5 (mais le fait qu'on n'indique ou pas les types n'a rien à voir avec le typage dynamique ou statique).
Mais rien n'empêche, en réalité, de procéder à un type check avant l'exécution, de façon statique. C'est, en gros, ce que Mypy veut faire. Si ça t'intéresse : http://mypy-lang.org/ .

+0 -0

D'abord un langage sans typage n'existe pas, ou alors il manipule toujours strictement le même type de données, ce qui le rend plutôt inutile. Bref, tout le monde a plus ou moins faux.

nohar

Bah il y a brainfuck mais pour la peine il est vraiment inutile :)

« La Nature est un livre écrit en langage mathématique », Galilée

+0 -1

Vu que l'on commence à tourner en rond, je propose de se lancer. Sachant qu'il y a des désaccords je propose de créer un groupe qui développera un Lisp-like interprété fonctionnel en Python. Cela peut intéresser les débutants, mais aussi les utilisateurs avancés qui veulent s'amuser.

Le but du langage serait d'avoir un système d'extension en Python, toutes les commandes seront des extensions en Python ou dans le langage lui-même.

Les volontaires peuvent me contacter par MP se manifester ici :)

Édité par the_new_sky

"C’est nuageux par ici"

+3 -1

Pourquoi ne pas discuter ici ?

D’autant que de mon point de vue, le programme devrait avoir un nombre aussi limité de commandes « natives » que possible, et développer toutes les commandes plus intelligentes dans le langage lui-même.

Ce qui implique de pas mal réfléchir sur la manière d’exprimer des commandes usuelles à partir d’autres commandes usuelles, pour choisir lesquelles on garde en natif. :)

#JeSuisGrimur #OnVautMieuxQueÇa

+6 -0

Aucun problème pour en discuter ici (je vais même éditer mon message), je voulais juste éviter que la conversation dérive trop et se mélange aux potentiels autres groupes. Si des volontaires se manifestent, on créera un topic séparé. ;)

Je pense que le langage devrai être le plus léger et modulaire possible, on code l'essentiel (en natif ou en "zlang", en priorité) et on permet de créer de nouvelles commandes dans le langage qui implémente l'interpréteur et en "zlang". Comme cela, les volontaires pourrons soit s'occuper du core léger du langage, soit créer un module pour une potentielle bibliothèque standard. :)

En passant, j'ai un idée de nom : Acid qui rappelle le goût des agrumes et qui est le contraire du célèbre langage BASIC.

Édité par the_new_sky

"C’est nuageux par ici"

+3 -1

Si vous faites un langage qui s'exécute dans l'interpréteur Python, il n'est peut-être en effet pas nécessaire de tout recoder à la main : se laisser la possibilité d'interagir avec le runtime Python, c'est s'ouvrir énormément de fonctionnalités de la bibliothèque standard et même la possibilité d'interagir avec du C.

Ça peut permettre de decouvrir plein de choses.

Édité par nohar

I was a llama before it was cool

+4 -0

Votre idée de lisp-like à l'air très bien :)
Malheureusement je ne suis pas trop familiarisé avec lisp.
Alors je propose de faire un second groupe pour créer le langage zScript. C'est un langage de script interprété par Python avec ply comme lexer/parser. Le but étant de créer un petit langage de script orienté objet de type python/ruby like. On commence doucement en créer juste un petit script de base avec des variables et des block if/else. Et pour ce qui ne connaissent pas ply c'est une façon de les initier au lexer/parser en Python. On mettra le projet sur github et chacun s'occupera d'une branche et pourra demander de l'aide au plus expérimenter sur le forum. Ensuite quand le langage sera "assez" fonctionnel, on rédigera une bibliothèque standard en zScript afin d'implémenter quelques fonctions basiques.

Voilà, je ne sais pas si ça vous convient mais ceux qui sont intéressés, faites moi part de vos avis :P

Quoi ? Qui veut un autographe ?

+2 -2

Vous pensez qu'il est envisageable de participer sans rien connaîte a Lisp et encore moins au fonctionnel ? :p

Je suis pas débutant mais j'ai jamais touché au fonctionnel, même si ça m'intéresse je n'ai pas encore eut le temps (disons que les réseaux de neurones ont frappé à ma porte)

+3 -0

Je ne connais pas lisp non plus, mais je me suis quand même incrusté. :)

Sinon, maintenant que l'on a une idée plus précise du langage, que dîtes-vous de créer un sujet avec un titre plus pertinent (du genre : "Un langage lisp-like créé en Python par des agrumes : Acid"), pour discuter des spécifications, et autres "détails" ?

Enfin, avez-vous un cours de python (en anglais ou en français peu m'importe) à me conseiller pour l'étudier un peu sérieusement (je ne connais que les bases) ?

Plus on apprend, et, euh… Plus on apprend. | Apprendre le C++ moderne

+0 -0

Si tu as déjà utilisé un langage type javascript où il est courant de passer une fonction en paramètre d'une autre et que tu es à peu près familier avec la récursivité, découvrir un langage fonctionnel ne devrait pas être trop compliqué.

+2 -0

Si certains sont vraiment enthousiastes pour un lisp, je leur conseille la lecture de l'excellent "Principes d'implantation de Scheme et Lisp" de Christian Queinnec. C'est un bouquin qui permet d'appréhender le langage en même temps que son implantation. Perso, il m'a bien aidé pour la réalisation d'un mini lisp.

Le chapitre 1 est disponible ici pour se faire une idée.

Sinon, il y a aussi ceci qui est plutôt rigolo.

+3 -0

Si tu as déjà utilisé un langage type javascript où il est courant de passer une fonction en paramètre d'une autre et que tu es à peu près familier avec la récursivité, découvrir un langage fonctionnel ne devrait pas être trop compliqué.

Berdes

Tout va bien alors ^^. Le passage de fonction en paramètre… :3

Moi j'aime pas le nom Acid, je trouve ça trop aggressif, j'aurais bien vu quelque chose en référence à Clémentine. Le plus évident serait bien sûr Clem, mais Kumquat ou Mandarine me paraît suffisamment décalé et sérieux pour être sympa. Sinon il y à Citrange que j'aime bien (mix entre un citron et une orange ^^)

+1 -2

On s'en fout du nom. Vous pouvez lui donner un nom qui ressemble à un hash sha512 qu'on s'en taperait encore le coquillard avec une cuisse de grenouille.

Et Ricocotam, c'est tout à fait accessible le fonctionnel, et on a des gens ici comme notre Carnufex et autres qui s'y connaissent et qui pourront répondre aux questions en cas de besoin. En plus, si tu apprends un peu de notions de fonctionnel avec cet atelier tu pourras les réutiliser (même en Python !) et tu auras moins peur de la récursivité. Perso je jongle entre fonctionnel et impératif selon le problème et je regrette pas une seule seconde. Ça rend meilleur programmeur de découvrir d'autres paradigmes.

+4 -0

J'ai un peu réfléchi à la question, et voici quelques idées et suggestions pour la #TeamFonctionnel. Vous êtes évidemment libres de les accepter ou non, de les compléter, de faire ce que vous voulez…

Les fonctionnalités du langage

Les lambdas

À mon avis, le cœur d'un langage essentiellement fonctionnel, et donc la première chose à définir, ce sont les lambdas, ou fonctions anonymes. Une lambda serait définie par le mot-clé lambda, une liste d'arguments, et un corps de fonction. Par exemple, une lambda qui additionne les carrés de deux nombres prendrait la forme suivante.

1
2
3
(lambda (x y) (
    (+ (* x x) (* y y))
))

Pourquoi définir des lambdas plutôt que des fonctions ? Pour deux raisons. Premièrement, parce qu'un langage fonctionnel puissant se doit de pouvoir passer une fonction en argument à une autre fonction, et renvoyer des fonctions : ces fonctions n'ont pas nécessairement besoin d'être nommées, et il est plus simple d'avoir un mot-clé pour définir une lambda, et un autre pour donner un nom à un objet, qu'un mot-clé pour les fonctions avec un nom, et un autre pour les fonctions anonymes.

Deuxièmement, parce que par leur seul présence, on donne la possibilité d'utiliser des fonctions curryfiées (c'est-à-dire ayant déjà reçu une partie de leurs arguments). Par exemple, la fonction curryfiée (-1) de Haskell se traduirait en zLang par ce code.

1
(lambda x (- x 1))

Le nommage

Ce n'est pas tout de dire qu'on va avoir des fonctions nommées, encore faut-il avoir le mot-clé pour le faire. Celui-ci est tout trouvé : define, suivi d'un nom, puis d'un objet à nommer (qui pour l'instant, ne peut être qu'une lambda).

1
2
3
(define carrédelhypothénuse (lambda (x y) (
    (+ (* x x) (* y y))
)))

Les types natifs

Pour un certain nombre de types, il est plus facile de les définir nativement. À priori, les types natifs suivants suffisent :

  • Int ;
  • Int8, Word8, Int16, etc. ;
  • Float et Double (peut-être même juste le second) ;
  • Char (qui représente un caractère Unicode, par un entier sur 8 bits) ;
  • tuple … (par exemple, (tuple Int Char Word64)).

Comme on le verra un peu plus loin, on peut se passer de définir des booléens nativement, on pourra le faire dans la bibliothèque standard. Idem pour les chaînes de caractères, qui seront définis à partir du type liste chaînée qu'on définira dans la bibliothèque standard.

Les types algébriques

Pour ceux qui ne seraient pas familiers de la programmation fonctionnelle, les types algébriques sont un moyen de définir n'importe quel type très simplement par une combinaison de deux procédés :

  • les types produits, qui ne sont rien d'autre que des tuples nommés, par exemple Point Double Double (c'est du Haskell, comprendre, un Point est la réunion de deux Double) ;
  • les types sommes, qui permettent à un type de prendre plusieurs formes (on dit qu'il a plusieurs constructeurs). D'où l'exemple suivant, toujours en Haskell.
1
data Bool = False | True

Il faut comprendre, un Bool peut-être soit un False, soit un True. Et là où cela devient vraiment puissant, c'est qu'on peut combiner les deux. Voici comment une liste chaînée est usuellement définie.

1
data List a = Nil | Cons a (List a)

En français : une liste de a (List est un type paramétré, on peut mettre n'importe quoi dedans) est, soit une liste vide appelée Nil, soit un Cons, qui est la réunion d'un a et d'une liste de a.

Dans ma première proposition, je suggérais de laisser de côté les types paramétrés dans un premier temps. Je retire cette suggestion, parce que trois des types les plus utiles de la programmation fonctionnelle (les listes, les options (Maybe en Haskell, Option en Rust) et les alternatives (Either en Haskell, Result en Rust)) sont tous paramétrés. On perdrait beaucoup à ne pas les avoir.

Alors voici ma proposition. Pour définir un type, on doit définir un ou plusieurs constructeurs, prenant des types en paramètres. Le type lui-même peut prendre des paramètres, qui peuvent ensuite être utilisés dans les constructeurs. Voici le type Option du Rust, tel qu'il serait défini en zLang.

1
2
3
4
(define Option (type a (
    None
    (Some a)
)))

Le filtrage par motifs

Pas de grande difficulté là-dedans, une commande match, qui prend une expression, et une série de combinaisons valeur / motif + corps de fonction. Par exemple, une fonction qui fait la somme des éléments d'une liste.

1
2
3
4
5
6
(define somme (lambda liste (
    match liste (
        (Nil 0)
        ((Cons val suite) (+ val (somme suite)))
    )
)))

C'est pour cette raison qu'on peut se contenter de définir dans la bibliothèque standard un type Bool plutôt que d'en faire un type natif. Ce qui en Haskell s'exprimerait comme if x < 3 then x + 2 else x - 6 peut s'exprimer en zLang comme suit.

1
2
3
4
match (< x 3) (
    (True (+ x 2))
    (False (- x 6))
)

Une question qui reste posée est la suivante : faut-il qualifier les constructeurs ? C'est-à-dire que, quand on l'utilise en dehors de sa définition même, doit-on écrire Bool::True (syntaxe de Rust) ou simplement True (syntaxe de Haskell) ? Dans la plupart des cas, la première solution est la plus pratique : ça permet d'avoir plusieurs types qui ont un constructeur portant le même nom, comme None. Mais pour quelques types (comme les booléens), c'est vraiment plus simple de donner le constructeur directement.

Quatre solutions possibles.

  1. Les constructeurs sont toujours qualifiés.
  2. Les constructeurs ne sont jamais qualifiés (Haskell).
  3. Les constructeurs sont qualifiés par défaut et un mot-clé supplémentaire permet de déqualifier les constructeurs d'un type donné.
  4. Les constructeurs sont qualifiés par défaut mais le mot-clé permettant d'importer dans l'espace de nom courant le contenu d'un module permet d'importer les constructeurs d'un type donné, traité comme s'il était un module (Rust).

Je préfère la dernière solution, même si le mot-clé en question ne deviendra vraiment utile que si l'on finit par ajouter un système de modules.

Les contraintes de types

Comment dire qu'une fonction doit avoir un type donné ? Ou qu'une expression donnée au sein d'une expression plus vaste doit avoir un type donné ? À l'aide du mot-clé hastype, qui s'utilisera de deux façons différentes, comme par exemple, ce qui suit.

1
2
3
(hastype (Double -> Double -> Double) carrédelhypothénuse)

(lambda x (+ x (hastype Word8 42)))

Le premier est pour les éléments qui ont un nom, le second pour les éléments anonymes.

Les entrées-sorties

Si on reste très basiques, on peut se contenter de deux fonctions, getchar et putchar. La principale difficulté, c'est quel type donner à ces fonctions ? Comment faire pour exécuter plusieurs de ces instructions à la suite, comme dans un langage impératif. Il existe une multitude de solutions possibles, n'impliquant pas nécessairement une monade. Je vous laisse y réfléchir. :)

Un peu de méta

Bon, c'est cool le filtrage par motif, mais la syntaxe utilisée pour une simple condition n'est vraiment pas intuitive, on préférerait avoir if condition cas_true cas_false. Eh bien, c'est tout à fait possible. On pourrait définir ceci dans la bibliothèque standard.

1
2
3
4
5
6
7
8
(hastype (Bool -> a -> a -> a) if)

(define if (lambda (cond cas_true cas_false) (
    match cond (
        (True cas_true)
        (False cas_false)
    )
)))

Mais ce n'est pas toujours possible ainsi. Comment définir un mot-clé function qui prend un nom, une liste d'arguments et un corps de fonction, et qui en fait une combinaison de define et de lambda ? Je n'ai pas encore trouvé de solution satisfaisante, alors je vous invite à y réfléchir.

Des commentaires

Le système du C/C++ avec // et /* */ me paraît très bien.

Ce que doit faire l'interpréteur / compilateur

  • Vérifier qu'il n'y a aucune erreur de syntaxe pure (manque une parenthèse, etc.).
  • Vérifier que tous les noms utilisés ont été définis à un endroit qui les rend visibles à l'endroit où ils sont utilisés.
  • Vérifier que toutes les fonctions définies au niveau du programme ont une déclaration de type quelque part.
  • Vérifier que les déclarations de type sont cohérente entre elles.
  • Interpréter / compiler.

#JeSuisGrimur #OnVautMieuxQueÇa

+14 -1
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

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