(Octobre 2015) Créez une calculatrice

a marqué ce sujet comme résolu.

Bonjour,
Je vous présente la première partie que j'ai commencée ce matin. Voici la tête de la calculatrice :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
La calculatrice Polonaise
Tapez 'quit' ou 'q' pour quitter le programme.
Tapez 'clear' ou 'c' pour nettoyer la calculatrice.

[1]: 5
[1]= 5.0

[2]: 9
[2]= 9.0

[3]: 9 9 +
[3]= 18.0

[4]: 9 9 + 5 4 98 */ -
ERREUR : La syntaxe entrée est invalide.
[4]: 9 9 + 5 4 98 * / -
[4]= 17.987244897959183

[5]: 

Et le code :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python3
# coding: utf-8

# Made by Louis Etienne

import os
import re

class UserRequest(Exception):
   '''
   Exception envoyée quand l'user demande quelque chose
   '''
   pass

CONTROLS = ['QUIT', 'Q',
           'CLEAR', 'C']

OPERATORS = ['+', '*', '-', '/', '^']

def clear():
   '''
   Nettoye le terminal
   '''
   os.system('cls' if os.name == 'nt' else 'clear')


def is_number(s):
   '''
   Vérifie que le nombre soit bien un flottant
   Retourne True si vrai, False si non
   '''
   try:
       float(s) # pour int et float
   except ValueError:
       return False
   else:
       return True


def check_format(entry):
   '''
   Prend l'entrée de l'user et vérifie :
       - qu'elle ne contient pas de lettre
       - que les chars sont séparé par des espaces
   Retourne la liste si tout est valide, autrement retourne False
   '''
   # On vérifie qu'il n'y ai pas de lettre
   if re.search('[a-zA-Z]', entry):
       return False

   else:
       # On vérifie que les chars sont bien séparé par des espaces
       list_chars = entry.split(' ')

       # On supprime les "" si il y a
       list_chars = [item for item in list_chars if item != '']

       for char in list_chars:
           if not is_number(char) and not char in OPERATORS:
               return False

       # Si l'entrée a passé tous les tests
       return list_chars


def calc(entry):
   '''
   Effectue les calculs et retourne le résultat
   '''
   result = []  # La pile
   error = False
   for char in entry:
       if is_number(char):
           result.append(float(char))
       else:
           try:
               nb1 = result.pop()
               nb2 = result.pop()

               nb1, nb2 = nb2, nb1
           except IndexError:
               print('ERREUR : Pas assez d\'opérateurs pour l\'opération \'{}\'.'.format(char))
               error = True
           else:
               if char == '+':
                   result.append(float(nb1 + nb2))
               elif char == '-':
                   result.append(float(nb1 - nb2))
               elif char == '*':
                   result.append(float(nb1 * nb2))
               elif char == '/':
                   try:
                       result.append(float(nb1 / nb2))
                   except ZeroDivisionError:
                       print('ERREUR : Impossible de diviser par 0.')
                       error = True
               elif char == '^':
                   result.append(float(pow(nb1, nb2)))
               else:
                   error = True
   return result[0] if not error else None


def main():
   '''
   Boucle principale du programme.
   '''
   print('La calculatrice Polonaise')
   print('Tapez \'quit\' ou \'q\' pour quitter le programme.')
   print('Tapez \'clear\' ou \'c\' pour nettoyer la calculatrice.')
   print()

   loop = True
   nb_calc = 0

   while loop:
       # On vérifie que l'user ne rentre pas n'importe quoi
       try:
           entry = input('[{}]: '.format(nb_calc + 1))

           if entry.upper() not in CONTROLS:
               entry = check_format(entry)
               assert entry
           else:
               raise UserRequest

       except UserRequest:
           # Si l'user demande de quitter le programme
           if entry.upper() in ['QUIT', 'Q']:
               loop = False
           # Si l'user demande de nettoyer le terminal
           elif entry.upper() in ['CLEAR', 'C']:
               clear()

       except KeyboardInterrupt:
           print('') # Force le terminal à passer à la ligne
           loop = False

       except AssertionError:
           print('ERREUR : La syntaxe entrée est invalide.')

       else:
           nb_calc += 1
           result = calc(entry)
           if result is not None:
               print('[{}]= {}\n'.format(nb_calc, result))

if __name__ == '__main__':
   main()

Je continue sur la partie 2 !

+1 -0

Voilà ma contribution, en Common Lisp.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(defpackage #:zds-calc
  (:use #:common-lisp)
  (:nicknames #:calc)
  (:export #:calc))

(in-package #:zds-calc)

(defvar *stack*)
(defparameter *dict* (make-hash-table :test #'eq))

(defmacro defprimitive (name &rest code)
  `(setf (gethash ',name *dict*) (lambda () ,@code)))

(defmacro calc (&rest words)
  `(let ((*stack*))
     (dolist (w ',words)
       (cond
         ((symbolp w) (handle-word w))
         ((numberp w) (push w *stack*))
         (t (error "Unknown word: ~S" w))))))

(defun handle-word (w)
  (multiple-value-bind (f exist?) (gethash w *dict*)
    (if exist?
      (funcall f)
      (error "Unknown word: ~S" w))))

(defprimitive +
 (push (+ (pop *stack*) (pop *stack*)) *stack*))

(defprimitive -
 (let ((op2 (pop *stack*))
       (op1 (pop *stack*)))
   (push (- op1 op2) *stack*)))

(defprimitive *
 (push (* (pop *stack*) (pop *stack*)) *stack*))

(defprimitive /
 (let ((op2 (pop *stack*))
       (op1 (pop *stack*)))
   (push (/ op1 op2) *stack*)))

(defprimitive ^
 (let ((op2 (pop *stack*))
       (op1 (pop *stack*)))
   (push (expt op1 op2) *stack*)))

(defprimitive drop
 (pop *stack*))

(defprimitive print
 (print (pop *stack*)))

(defprimitive swap
 (rotatef (car *stack*) (cadr *stack*)))

(defprimitive print-stack
 (print *stack*))  

Un petit exemple d'utilisation:

1
(calc 3.4 2 / 4 + print) ; => 5.7

Je n'ai pas implémenté la notation infixe (j'éditerai sans doute plus tard). En l'état, je pourrais "presque" implémenter un interpréteur/compilateur Forth.

+1 -0

La première partie en Haskell:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module Calculator where

import Data.Char (isDigit)
import Data.List (span)

data Token = Number Double | Add | Sub | Mul | Div | Pow

isOperator :: Token -> Bool
isOperator (Number _) = False
isOperator _          = True

readToken :: String -> Token
readToken str
    | str == "+"      = Add
    | str == "-"      = Sub
    | str == "*"      = Mul
    | str == "/"      = Div
    | str == "^"      = Pow
    | all isDigit str = Number (read str :: Double)

type Stack = [Token]

readStack :: String -> Stack
readStack str = map readToken (words str)

eval :: Stack -> Double
eval []           = error "Empty stack"
eval [(Number n)] = n
eval [_]          = error "Non number in last operator call"
eval stack        = eval ((Number $ call op args) : newStack)
    where (args, (op:newStack)) = span (not . isOperator) stack

call :: Token -> [Token] -> Double
call op xs
    | any isOperator xs = error "Non number argument"
    | otherwise = let (arg:args) = (map (\(Number x) -> x) xs) in
        (\ f -> foldl f arg args) $ case op of
            Add -> (+)
            Sub -> (-)
            Mul -> (*)
            Div -> (/)
            Pow -> (**)

repl :: IO ()
repl = putStr "> " >> getLine >>= \ input ->
    if input == "q" || input == "quit"
    then return ()
    else (print . eval . readStack) input >> repl

Edit: Je viens de me rendre compte en relisant l'énoncé que les opérateurs n'acceptaient que deux opérandes, alors que dans ce code ci on peut tout à fait écrire 1 2 3 + (ce qui donnera 6). Par contre c'est embêtant quand on veut écrire des choses comme 6 5 2 * + qui donne avec mon code 60 ((6 5 2 *) +) mais on obtient avec l'algorithme dans l'énoncé 16 (6 (5 2 *) +).

Certains passages de mon code sont crades aussi, ou un peu tordus. Ça pourrait être plus propre.

+1 -1

Je débute en OCaml, voici donc ma version (simpliste) pour le niveau 1. Je continuerais… si j'ai un peu de temps ;)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
open Core.Std

let operation stack op =
    match stack with
    | [] | [_] as l -> l
    | right::left::stack -> ( op left right)::stack

let eval_postfix l =
    let rec loop l stack =
        match l with 
        | [] -> stack
        | "+" :: tl -> loop tl (operation stack ( +. ))
        | "-" :: tl -> loop tl (operation stack ( -. ))
        | "*" :: tl -> loop tl (operation stack ( *. ))
        | "/" :: tl -> loop tl (operation stack ( /. ))
        | "^" :: tl -> loop tl (operation stack ( ** ))
        | hd :: tl -> loop tl ((Float.of_string hd)::stack)
    in
    match loop l [] with
    | [] -> 0.0
    | hd::_ -> hd

let () = 
    let input = In_channel.input_line In_channel.stdin in  
    match input with
    | None -> failwith "Meh"
    | Some input_string -> printf "%F\n" (eval_postfix (String.split ~on:' ' input_string))

Le niveau 1 ayant l'air facil, j'ai décidé de m'y atteler dans un langage que je voulais voir depuis quelque temps, le Rust. Ça donne ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
use std::io;

fn main() {
    let mut pile: Vec<f64> = Vec::new();

    let mut entree = String::new();
    io::stdin().read_line(&mut entree)
        .ok()
        .expect("Failed to read line");
    println!("You guessed: {}", entree);
    //entree contient la chaine non formatté.

    let formatte: Vec<&str> = entree.split_whitespace().collect();

    for element in formatte {
        if element == "+" {
            let nombre = pile[pile.len()-1];
            let nombre2 = pile[pile.len()-2];
            pile.pop();pile.pop();pile.push(nombre+nombre2);
        } else if element == "-" {
            let nombre = pile[pile.len()-1];
            let nombre2 = pile[pile.len()-2];
            pile.pop();pile.pop();pile.push(-nombre+nombre2);
        } else if element == "*" {
            let nombre = pile[pile.len()-1];
            let nombre2 = pile[pile.len()-2];
            pile.pop();pile.pop();pile.push(nombre*nombre2);
        } else if element == "/" {
            let nombre = pile[pile.len()-1];
            let nombre2 = pile[pile.len()-2];
            pile.pop();pile.pop();pile.push(1./nombre*nombre2);
        } else if element == "^" {
            let nombre = pile[pile.len()-1];
            let nombre2 = pile[pile.len()-2];
            pile.pop();pile.pop();pile.push(nombre2.powf(nombre));
        } else {
            //C'est alors un nombre.
            let nombre: f64 = element.trim().parse()
                .ok()
                .expect("Please type a number!");
            pile.push(nombre);
        }
    }
println!("Resutlat : {}", pile[0])
}

Mes impressions sur ce langage, c'est qu'il vraiment casse-couille. En effet, le code suivant ne compile pas :

1
2
3
4
5
fn main() {
    let mut pile: Vec<f64> = vec![1.];//Création d'un vecteur/tableau/pile.
    let nombre = pile.pop();//On lui retire le dernier élément et on le met dans nombre.
    println!("Mon nombre : {}", nombre);//Affichage du nombre.
}

Avec un message d'erreur d'une clarté incroyable :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
plante.rs:5:33: 5:39 error: the trait `core::fmt::Display` is not implemented for the type `core::option::Option<f64>` [E0277]
plante.rs:5     println!("Mon nombre : {}", nombre);
                                            ^~~~~~
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
plante.rs:5:5: 5:41 note: expansion site
plante.rs:5:33: 5:39 help: run `rustc --explain E0277` to see a detailed explanation
plante.rs:5:33: 5:39 note: `core::option::Option<f64>` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
plante.rs:5     println!("Mon nombre : {}", nombre);
                                            ^~~~~~
note: in expansion of format_args!
<std macros>:2:25: 2:56 note: expansion site
<std macros>:1:1: 2:62 note: in expansion of print!
<std macros>:3:1: 3:54 note: expansion site
<std macros>:1:1: 3:58 note: in expansion of println!
plante.rs:5:5: 5:41 note: expansion site
error: aborting due to previous error
[1]    2515 exit 101   rustc plante.rs

(ne coyez pas que le « run rustc --explain E0277 to see a detailed explanation » va vous donner des explications claires : ça donne

You tried to use a type which doesn't implement some trait in a place which expected that trait. Erroneous code example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// here we declare the Foo trait with a bar method
trait Foo {
    fn bar(&self);
}

// we now declare a function which takes an object implementing the Foo trait
fn some_func<T: Foo>(foo: T) {
    foo.bar();
}

fn main() {
    // we now call the method with the i32 type, which doesn't implement
    // the Foo trait
    some_func(5i32); // error: the trait `Foo` is not implemented for the
                     //     type `i32`
}

In order to fix this error, verify that the type you're using does implement the trait. Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
trait Foo {
    fn bar(&self);
}

fn some_func<T: Foo>(foo: T) {
    foo.bar(); // we can now use this method since i32 implements the
               // Foo trait
}

// we implement the trait on the i32 type
impl Foo for i32 {
    fn bar(&self) {}
}

fn main() {
    some_func(5i32); // ok!
}

ce qui n'a rien à voir avec le code que j'ai compilé.)

En vrai, il nous dit que pop() renvoie soit un truc du type du vecteur, soit None. Or, si jamais c'est None, alors on ne peut faire println!. Ni + (comprendre nombre + 1.1 plante aussi). Sachant que les comparaison devienne aussi impossible, le simple fait de faire un pop devient méga-chiant.

Par contre le code suivant compile sans problème :

1
2
3
4
5
fn main() {
    let mut pile: Vec<f64> = vec![1.];
    let nombre = pile[pile.len()-1]; pile.pop();
    println!("Mon nombre : {}", nombre);
}

Alors qu'il fait très exactement la même chose que le précédant, au détail près que l'accès direct est plantogène. Officiellement, la langage se veut « sure et rapide ». Personnellement, je dirai qu'il est lourd, illogique, et tout aussi peu fiable que les autres.

+1 -0

Ouf, j'ai hésiter à faire ma version en Rust (que je n'ait jamais utilisé). J'ai essayé en ES6, mais comme n'est pas complètement implémenté dans tout les navigateurs, je suis obligé de bidouiller, d'utiliser un transpileur, et ça m'agace, du coup je vais bientôt poster une version en Ruby.

Et voilà ma version en Ruby :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def read ()
    puts("Veuillez entrer un calcul : ")
    input = gets.chomp.split(" ")
end

def calc (input)

    operations = ["+", "-", "*", "/"]
    stack = []

    for w in input
        if !operations.include?(w)
            stack << w.to_i
        else
            op1 = stack.pop
            op2 = stack.pop
            if w == "+"
                stack << op2 + op1
            elsif w == "-"
                stack << op2 - op1
            elsif w == "*"
                stack << op2 * op1
            elsif w == "/"
                stack << op2 / op1
            end
        end
    end

    return stack.pop

end

input = ""

loop do
    input = read()
    break if input == "exit"
    result = calc(input)
    puts result
end

Je continue sur la partie 2 !

Officiellement, la langage se veut « sure et rapide ». Personnellement, je dirai qu'il est lourd, illogique, et tout aussi peu fiable que les autres.

Gabbro

Pour le coup, je pense que tu as mal compris l'intérêt de la fonction pop. Elle retourne non pas ton objet, mais un objet de type Option qui correspond soit à une valeur, soit à aucune valeur. En effet, si ton vecteur est vide, ce n'est pas possible de retirer une valeur, tu te retrouves donc avec None. Le moyen le plus simple de traiter ce genre de cas est d'utiliser le patern-matching :

1
2
3
4
match pile.pop() {
    Some(nombre) => println!("Mon nombre : {}", nombre);
    None => println("La pile est vide");
}

Au passage, le compilateur vérifie que tous les cas soient traités dans le match, ce qui permet vraiment d'avoir quelque chose de bien plus sûr que ta solution avec un accès directe.

Petite note au passage : je ne connais pas du tout le Rust et tout ce que j'ai pu décrire précédemment vient essentiellement de mon expérience en programmation fonctionnelle et de 2 minutes de recherches. Le type Option ressemble en fait très fortement au type Maybe du Haskell, dont l'équivalent est probablement présent dans tous les langages fonctionnels. L'idée derrière ce genre de type est de lier une valeur à une potentielle erreur, forçant l'utilisateur à traiter l'erreur pour utiliser la valeur associée. En terme de sûreté, c'est très largement meilleur que ce que l'on peut avoir en C (potentiel segfault) ou en Java (exception pas obligatoirement traitée). C'est d'ailleurs ce genre de chose qui permet au Haskell (notamment) de faire facilement du code très fiable. Globalement, de tous les langages (compilés) que j'ai pu manipuler, c'est avec Haskell que l'affirmation "Ça compile, donc ça marche" est la mieux respecté, et je crois comprendre que c'est un des buts recherchés par Rust.

Voici ma version en Swift 2.0 de la première partie :

Fichier main.swift:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//
//  main.swift
//  Calculasticot
//
//  Created by Alexis on 03/10/2015.
//  Copyright © 2015 LAMASTICOT&CO. All rights reserved.
//

import Foundation

var pile: [Float] = [] // la pile
var nb_calc: Int = 1 // Numéro du calcul en cours
var calc: [String] = [] // La saisie de l'utilisateur
var loop: Bool = true // pour gérer la boucle
var nb1: Float? // l'opérande 1
var nb2: Float? // l'opérande 2
var error: Bool = false // en cas d'erreur

print("CALCULETTE EN NOTATION POLONAISE INVERSE (NPI)")
print("Taper 'quit' ou 'q' pour quitter'.")
print("Taper 'd' ou 'doc' pour afficher la documentation sur le fonctionnement de la calculette.")
print("Veillez à bien séparer par un espace tout ce qui peut l'être, sinon vous risquez d'avoir quelques problèmes.\n")

while loop {

  // Saisie de l'utilisateur
  // La fonction `readLine()` n'est pas utilisée car elle ne permet pas l'insertion d'un message comme ici
  calc = input("[\(nb_calc)]:").componentsSeparatedByString(" ")

  // On vérifie que l'utilisateur n'a pas entré une commande particulière, si c'est le cas on la prend en compte
  switch calc[0] {
      case "q", "quit":
          loop = false
      case "d", "doc":
          doc_notation_polonaise()
          nb_calc++
          continue // On passe directement à l'itération suivante après avoir mis à jour le nombre de calculs faits
  default:
      print("[\(nb_calc)]=", separator: "", terminator: " ")
  }

  // Dans le cas où l'utilisateur aurait demandé à quitter, ben on quitte
  if !loop {
      print("\nArrêt du programme ...\n")
      break
  }

  for i in calc {

      if let n = Float(i) {
          pile.append(n) // Gestion des nombres dans la pile
      } else {
                      // On récupère les opérandes
          nb1 = pile.popLast()
          nb2 = pile.popLast()

          // On empêche le programme de planter en cas de mauvaise saisie
          if nb1 != nil && nb2 != nil {

              // Gestion des opérateurs
              switch i {
                  case "+":
                      pile.append(nb2! + nb1!)
                  case "-":
                      pile.append(nb2! - nb1!)
                  case "*":
                      pile.append(nb2! * nb1!)
                  case "/":
                      // Attention à la division par zéro
                      if nb1 == 0 {
                          print("La division par zéro est impossible")
                          error = true
                      } else {
                          pile.append(nb2! / nb1!)
                      }
                  case "**":
                      pile.append(pow(nb2!, nb1!))
              default:
                  print("ERREUR : Vous avez fait une erreur de saisie quelque part.")
                  error = true
              }
          } else {
              print("ERREUR : Vous avez fait une erreur de saisie quelque part.")
              error = true
          }
      }
  }

  // Affichage du résultat, s'il n'y a pas eu d'erreur de saisie ou de calcul
  if !error {
      print(pile.popLast()!)
  } else {
      error = false // On remet 'error' à false pour le prochain tour de boucle, dans le cas où il serait passé à true
  }

  // Incrémentation du compteur de tour de calcul
  nb_calc++
  print("")
}

Et le fichier fonctions.swift:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
//  fonctions.swift
//  Calculasticot
//
//  Created by Alexis on 03/10/2015.
//  Copyright © 2015 LAMASTICOT&CO. All rights reserved.
//

import Foundation

/**
Fonction qui permet une saisie de l'utilisateur dans la console et qui retourne cette saisie sous forme de `String`.

Le paramètre `message` est optionnel. Il permet d'afficher un message non-vide avant la saisie de l'utilisateur.

Exemple d'utilisation :

let theInput = input("Please enter your input:")
print(theInput)

*Remarque : un espace est automatiquement ajouté à la fin du message d'invitation à la saisie.*
*/
func input(message: String = "") -> String {

  if !message.isEmpty {
      print(message, separator: "", terminator: " ")
  }

  let keyboard = NSFileHandle.fileHandleWithStandardInput()
  let inputData = keyboard.availableData
  return (NSString(data: inputData, encoding: NSUTF8StringEncoding) as! String).stringByReplacingOccurrencesOfString("\n", withString: "")
}


/**
Une simple fonction d'affichage de texte, qui permet à l'utilisateur de se familiariser avec la notation polonaise.
*/
func doc_notation_polonaise() -> Void {
  print("== DOCUMENTATION ==")
  print("La notation polonaise inverse est une notation dite \"post-fixée\", c'est à dire que les opérateurs sont toujours écris après les opérandes. L'avantage est qu'il n'y a plus besoin de parenthèses, et que tous les opérateurs ont la même priorité.")
  print("Concrètement, si on veut écrire 2 + 3 en NPI, on place simplement le + à la fin de l'expression, comme ceci : 2 3 +.")
  print("\nEt pour des expressions plus compliquées ?\n")
  print("(2 + 3) * 5 devient 5 2 3 + * (le + s'applique aux deux opérandes qui le précèdent, 2 et 3, et le * s'applique au résulat du + et à l'opérande qui précède, 5), ou bien encore 2 3 + 5 *. (2 + 3) * (7 - 4) devient ainsi 2 3 + 7 4 - *.)\n")
  print("Vous pouvez utilisez les opérateurs suivants : +, -, *, / et ** (puissance).\n")
}

Quelques remarques sur le code:

  • Il est assez crade et pas du tout optimisé je pense.
  • Normalement, j'ai géré la grande majorité des erreurs possibles (il doit rester les nombres trop grands ou trop petits pour être représenté par l'ordinateur mais sinon ça passe je pense).
  • Mon opérateur de puissance est ** car l'accent circonflexe ne passe pas pour je ne sais quelle raison.
  • La fonction input est recodée car elle n'existe pas naturellement en Swift (ce qui est super énervant). EDIT : En fait une fonction readline() existe (merci à Quark67) mais je ne l'utilise toujours pas car elle ne permet pas l'insertion d'un message comme le fait ma fonction input (piquée sur StackOverflow, avouons-le, puis un peu modifiée pour permettre l'insertion du message, comme cela peut se faire en Python par exemple).
  • En Swift les imports des fichiers tiers vers le main se font automatiquement, ils ont juste à être dans le même dossier.
+0 -0

Berdes, je me doutait qu'il fallait faire un truc comme ça (même si j'aurai été incapable de le faire), mais ce n'est pas ce qu'encourage la doc. Et mes connaissance en Rust ou en fonctionnel ne me permettaient pas de deviner.

Merci pour l'exemple. Cependant, ça confirme à mon sens le côté illogique du langage. S'il faut faire du patern-matching, on n'autorise pas pile[pile.len()-1] à passer sans problème (même pas de warning, ni rien). C'est surtout ça que je lui reproche : un problème de cohérence (et le message du compilateur).

Personnellement, je trouve que devoir faire un match à chaque pop est lourd, mais ça se justifie.

J'ai l'impression qu'ils mélangent des concepts venus du fonctionnel dans un langage impératifs, tout en se positionnant en remplacement du C++ (ou du Python).

+0 -0

@Gabbro: Je sais pas, la doc que tu cites indique que pop retourne un Option<T>. D'ailleurs je trouve le message d'erreur de compilation est explicite : on sait dès la première ligne ce qui ne va pas. Sinon, pour éviter le match tu peux écrire stack.pop().expect("Empty stack").

Pour le coup, je suis d'accord avec toi sur le fait que laisser la possibilité d'un accès non protégé n'est pas parfait pour la fiabilité potentielle (il existe aussi unwrap pour récupérer le contenu d'un Option). Il faut cependant se rendre compte que le code est plus lourd à écrire avec et que ça devient clairement inutile quand la logique permet de prouver que l'accès se fait toujours sur une valeur existante. C'est clairement un compromis à faire entre la fiabilité du code, la facilité d'écriture et les performances.

En effet, la doc ne montre pas grand chose sur la façon d'utiliser un Option, ce qui n'est pas évident lorsque l'on ne connais pas ce genre de concepts. Je suppose que c'est dû au fait qu'ils suppose que les personnes qui lisent la doc ont déjà acquis les principes du langages.

Au passage, si je comprends bien le message du compilateur, c'est surtout le fait qu'il ne sait pas afficher un Option qui lui pose problème. Là encore, il y a des éléments inspirés du fonctionnel puisqu'il manque la caractéristique (trait en Rust, class en Haskell) Display à Option. À toi d'en déduire que nombre est un Option et qu'il faut le transformer en quelque chose d'affichable (ou implémenter le trait affichable pour Option). Je te laisse regarder la doc à ce sujet : https://doc.rust-lang.org/book/traits.html .

La fonction input est recodée car elle n'existe pas naturellement en Swift (ce qui est super énervant).

C'est plutôt surprenant pour un langage aussi moderne.
Après, on peut relativiser et se dire qu'inclure quelques lignes de codes, toujours identiques, ça ne mange pas de pain.

C'est plutôt surprenant pour un langage aussi moderne.
Après, on peut relativiser et se dire qu'inclure quelques lignes de codes, toujours identiques, ça ne mange pas de pain.

Emeric

Oui, une fois qu'on la fait, on le copie colle quelque part et c'est bon.

En fait je pense qu'elle n'y est pas car Swift a été pensé pour créer des applis graphiques, pour Mac ou iBidule, donc pas besoin d'une fonction input() qui ne serait utilisable qu'en console. Ça va peut être changer vu que Swift va être portable vers Linux.

+0 -0

Sujet super intéressant ! :)

J'ai pris un peu de temps ce week-end pour implémenter un peu à ma façon le niveau 2 en C#. Plutôt que d'aller chercher à implémenter un algo, j'ai essayé de faire par moi-même. Il peut ainsi encore y avoir quelques bugs, le code est peut-être inutilement compliqué et pas optimisé, il n'est pas explicite sur les erreurs. Je verrai ça avec les améliorations du défi pour plus tard ! :) Il prend en charge en + du niveau 2 le moins unaire (-4+5 ou 5+(-4) par exemple).

J'ai utilisé l'architecture d'un mini-compilateur, avec lexer, parser et arbre.

Voici le code ; si vous voulez le tester, copiez le code dans un fichier et appeler la méthode static PMToolsDLL.Calculatrice.Calculateur.CalculerExp(string votreexpression)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
// florian6973. (C) Tous droits réservés. 2015.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PMToolsDLL.Calculatrice
{
    // + - * / ^ ( )
    public class Calculateur
    {
        public static String CalculerExp(string exp)
        {
            string ret = "";

            Lexer lx = new Lexer(exp);

            Parser ps = new Parser(lx);
            ps.Parse();

            ret = EXE.Executer(ps._ast);

            return ret;
        }

        class Lexer
        {
            public enum Token
            {
                EOF = 0,
                Add = 1,
                Sous = 2,
                Mult = 3,
                Div = 4,
                Puiss = 5,
                ParG = 6,
                ParD = 7,
                Chiffre = 8,
                Virgule = 9,
            }

            public String _exp = "";

            public Lexer(string exp)
            {
                this._exp = exp;
            }

            int _curpos = 0;

            public char GetCurrentToken()
            {
                return this._exp[this._curpos - 1];
            }

            public Token ReadNext()
            {
                if (this._curpos < _exp.Length)
                {
                    Token t = Token.EOF;

                    switch (this._exp[this._curpos])
                    {
                        case '+':
                            t = Token.Add;
                            break;
                        case '-':
                            if (this._curpos == 0)
                                t = Token.Chiffre;
                            else if ((this._exp.Length > 1) && (this._curpos > 0) && (this._exp[this._curpos - 1] == '('))
                                t = Token.Chiffre;
                            else
                                t = Token.Sous;
                            break;
                        case '*':
                            t = Token.Mult;
                            break;
                        case '/':
                            t = Token.Div;
                            break;
                        case '^':
                            t = Token.Puiss;
                            break;
                        case '(':
                            t = Token.ParG;
                            break;
                        case ')':
                            t = Token.ParD;
                            break;
                        case '.':
                            t = Token.Virgule;
                            break;
                        default:
                            if (Char.IsNumber(this._exp[this._curpos]))
                                t = Token.Chiffre;
                            break;
                    }

                    this._curpos++;

                    if (t == Token.EOF)
                        throw new Exception(string.Format("Caractère à la position {0} non reconnu !", (this._curpos - 1)));
                    else
                        return t;
                }
                else
                    return Token.EOF;
            }
        }

        class Parser
        {
            public Lexer _lx = null;
            public AST _ast = null;

            public Parser(Lexer lx)
            {
                this._lx = lx;
                this._ast = new AST();
            }

            ParseStack<Nombre> _nbs = new ParseStack<Nombre>();
            ParseStack<Operation> _opsD = new ParseStack<Operation>();
            ParseStack<Operation> _opsG = new ParseStack<Operation>();
            ParseStack<bool> _tyopG = new ParseStack<bool>();
            ParseStack<Operation.TypeOp> _top = new ParseStack<Operation.TypeOp>();

            bool _precparent = false;
            bool _parentadr = false;

            private void AddExprs()
            {
                while ((_top.Count > 1) && ((Operation.OpPrio[(int)_top.Peek()].Value <= Operation.OpPrio[(int)_top.Get((_top.Count - 2))].Value) /*|| (first)*/ || (_precparent)) && !(_top.Peek() == Operation.TypeOp.Puissance))
                {
                    Operation tempop = new Operation();

                    tempop.OpOperateur = _top.Get(_top.Count - 2);

                    if ((_precparent) && (_top.Count > 1) && (_opsD.Count > 0))
                    {
                        if (Operation.OpPrio[(int)_top.Peek()].Value > Operation.OpPrio[(int)_top.Get(_top.Count - 2)].Value)
                            _parentadr = true;
                    }
                    else if ((_top.Count > 2)/* || (_precparent)*/)
                    {
                        if (_parentadr)
                        {
                            tempop.TermeD = _nbs.Pop();
                            tempop.OpG = _opsD.Pop();

                            _opsD.Push(tempop);

                            _parentadr = false;
                        }
                        else
                        {
                             bool spec = (((Operation.OpPrio[(int)_top.Get(0)].Value < Operation.OpPrio[(int)_top.Get(1)].Value) && ((Operation.OpPrio[(int)_top.Peek()].Value == Operation.OpPrio[(int)_top.Get((_top.Count - 2))].Value) || ((_top.Get(_top.Count - 2) == Operation.TypeOp.Puissance) && (_top.Count > 2) && (Operation.OpPrio[(int)_top.Get(_top.Count - 3)].Value < Operation.OpPrio[(int)_top.Get(_top.Count - 1)].Value)))));

                            if (_opsD.Count > 0)
                                tempop.OpD = _opsD.Pop();
                            else
                                tempop.TermeD = _nbs.Pop();

                            if ((_tyopG.Count > 0) && (!_tyopG.Peek()))
                            {
                                if (_nbs.Count > 0)
                                    tempop.TermeG = _nbs.Pop();
                                else
                                {
                                    tempop.OpG = _opsG.Pop();
                                    _tyopG.Pop();
                                }
                            }
                            else
                            {
                                if (_opsG.Count > 0)
                                {
                                    tempop.OpG = _opsG.Pop();
                                    _tyopG.Pop();
                                }
                                else
                                    tempop.TermeG = _nbs.Pop();
                            }

                            if (spec)
                            {
                                _opsG.Push(tempop);
                                _tyopG.Push(true);
                            }
                            else
                                _opsD.Push(tempop);
                        }
                    }
                    else
                    {
                        if (_nbs.Count > 1)
                        {
                            tempop.TermeD = _nbs.Pop();
                            tempop.TermeG = _nbs.Pop();
                        }
                        else if (_nbs.Count > 0)
                        {
                            if (_opsD.Count > 0)
                            {
                                tempop.OpD = _opsD.Pop();
                                tempop.TermeG = _nbs.Pop();
                            }
                            else // faire erreurs
                            {
                                tempop.OpG = _opsG.Pop();
                                _tyopG.Pop();

                                tempop.TermeD = _nbs.Pop();
                            }
                        }
                        else // faire err
                        {
                            tempop.OpG = _opsG.Pop();
                            _tyopG.Pop();

                            tempop.OpD = _opsD.Pop();
                        }

                        _opsG.Push(tempop);
                        _tyopG.Push(false);
                    }

                    if (_precparent == false)
                        _top.Remove(_top.Count - 2);
                    else
                        _precparent = false;
                }
            }

            public void SousExp(string sexp)
            {
                Lexer lx = new Lexer(sexp);

                Parser ps = new Parser(lx);
                ps.Parse();

                if (ps._ast._struct.Nb != null)
                    _nbs.Push(ps._ast._struct.Nb);
                else
                {
                    if ((_opsG.Count > 0) || (_nbs.Count > 0))
                    {
                        _opsD.Push(ps._ast._struct.OpBase);

                        _precparent = true;
                    }
                    else
                    {
                        _opsG.Push(ps._ast._struct.OpBase);

                        _tyopG.Push(false);
                    }
                }
            }

            public void Parse()
            {
                Lexer.Token cur = this._lx.ReadNext();
                string curexp = "";
                bool opone = false;

                while (cur != Lexer.Token.EOF)
                {
                    switch (cur)
                    {
                        case Lexer.Token.Chiffre:
                            curexp = this._lx.GetCurrentToken().ToString();
                            cur = this._lx.ReadNext();

                            while ((cur == Lexer.Token.Chiffre) || (cur == Lexer.Token.Virgule))
                            {
                                curexp += this._lx.GetCurrentToken().ToString();
                                cur = this._lx.ReadNext();
                            }

                            _nbs.Push(new Nombre() { Nb = curexp });

                            continue;

                        case Lexer.Token.ParG:
                            curexp = "";
                            cur = this._lx.ReadNext(); // faire erreur si ()
                            int countpar = 1;

                            while (true)
                            {
                                curexp += this._lx.GetCurrentToken().ToString();
                                cur = this._lx.ReadNext();

                                if (cur == Lexer.Token.ParG)
                                    countpar++;

                                if (cur == Lexer.Token.ParD)
                                    countpar--;

                                if (countpar == 0)
                                    break;
                            }

                            this.SousExp(curexp);

                            break;

                        default:
                            if ((cur == Lexer.Token.Add) || (cur == Lexer.Token.Sous) || (cur == Lexer.Token.Mult) || (cur == Lexer.Token.Div) || (cur == Lexer.Token.Puiss))
                            {
                                opone = true;

                                _top.Push((Operation.TypeOp)((int)cur - 1));

                                AddExprs();
                            }
                            break;
                    }

                    cur = this._lx.ReadNext();
                }

                if (opone)
                {
                    _top.Push(Operation.TypeOp.Addition);
                    AddExprs();

                    if (_opsD.Count > 0) // OU Operateur ?
                        this._ast._struct.OpBase = _opsD.Pop();
                    else
                        this._ast._struct.OpBase = _opsG.Pop();
                }
                else
                    this._ast._struct.Nb = _nbs.Pop();
            }
        }

        class AST
        {
            public ExpressionAlgebrique _struct = new ExpressionAlgebrique();

            public string Calc()
            {
                return this._struct.Calc();
            }

            public override string ToString()
            {
                return _struct.ToString();
            }
        }

        class EXE
        {
            public static String Executer(AST a)
            {
                return a.ToString() + " = " + a.Calc();
            }
        }

        public class ParseStack<T>
        {
            private List<T> items = new List<T>();

            public T Get(int i)
            {
                return items[i];
            }

            public int Count
            {
                get { return items.Count; }
            }

            public void Push(T item)
            {
                items.Add(item);
            }

            public T Peek()
            {
                return items[items.Count - 1];
            }

            public T Pop()
            {
                if (items.Count > 0)
                {
                    T temp = items[items.Count - 1];
                    items.RemoveAt(items.Count - 1);

                    return temp;
                }
                else
                    return default(T);
            }

            public void Remove(int itemAtPosition)
            {
                items.RemoveAt(itemAtPosition);
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PMToolsDLL.Calculatrice
{
    class Operation
    {
        public static List<KeyValuePair<string, int>> OpPrio = new List<KeyValuePair<string, int>>() { new KeyValuePair<string, int>("+", 0), new KeyValuePair<string, int>("-", 0), new KeyValuePair<string, int>("*", 1), new KeyValuePair<string, int>("/", 1), new KeyValuePair<string, int>("^", 2) };

        public enum TypeOp
        {
            Addition = 0,
            Soustraction = 1,
            Multiplication = 2,
            Division = 3,
            Puissance = 4,
            Null = 5
        }

        public Nombre TermeG = null;
        public Nombre TermeD = null;

        public TypeOp OpOperateur = TypeOp.Null;

        public Operation OpG = null;
        public Operation OpD = null;

        public override string ToString()
        {
            string ret = "[";

            if (this.TermeG != null)
                ret += this.TermeG.Nb;
            else
                ret += this.OpG.ToString();

            ret += Operation.OpPrio[(int)this.OpOperateur].Key;
            
            if (this.TermeD != null)
                ret += this.TermeD.Nb;
            else
                ret += this.OpD.ToString();

            ret += "]";
            return ret;
        }

        public string Calc()
        {

            decimal g = 0;
            decimal d = 0;

            if (this.TermeG != null)
                g = decimal.Parse(this.TermeG.Nb);
            else
                g = decimal.Parse(this.OpG.Calc());

            if (this.TermeD != null)
                d = decimal.Parse(this.TermeD.Nb);
            else
                d = decimal.Parse(this.OpD.Calc());

            decimal ret = 0;
            switch (this.OpOperateur)
            {
                case TypeOp.Addition:
                    ret = g + d;
                    break;
                case TypeOp.Soustraction:
                    ret = g - d;
                    break;
                case TypeOp.Multiplication:
                    ret = g * d;
                    break;
                case TypeOp.Division:
                    ret = g / d;
                    break;
                case TypeOp.Puissance:
                    ret = (decimal)Math.Pow((double)g, (double)d);
                    break;
                default:
                    throw new Exception("Opérateur inconnu !");
            }


            return ret.ToString();
        }
    }

    class Nombre
    {
        public string Nb = "";
    }
    
    // Pour + tard
    class Variable
    {
        public string Var = "";
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PMToolsDLL.Calculatrice
{
    class ExpressionAlgebrique
    {
        public Nombre Nb = null;

        public Operation OpBase = null;

        public override string ToString()
        {
            if (this.OpBase != null)
                return OpBase.ToString();
            else
                return Nb.Nb;
        }

        public string Calc()
        {
            if (this.OpBase != null)
                return OpBase.Calc();
            else
                return Nb.Nb;
        }
    }
}

Voilà, voilà ! En tout cas, je trouve ce défi très sympathique et intéressant ! :)

EDIT : Correction d'un bug avec les puissances 6/(5^5) et 2+(5^5)/6 par exemple

+0 -0

Berdes, je me doutait qu'il fallait faire un truc comme ça (même si j'aurai été incapable de le faire), mais ce n'est pas ce qu'encourage la doc. Et mes connaissance en Rust ou en fonctionnel ne me permettaient pas de deviner.

Si tes connaissances en Rust sont encore faibles, peut-être que ton jugement à l'emporte-pièce sur la cohérence du langage est un peu rapide, qu'en penses-tu ? :-)

Merci pour l'exemple. Cependant, ça confirme à mon sens le côté illogique du langage. S'il faut faire du patern-matching, on n'autorise pas pile[pile.len()-1] à passer sans problème (même pas de warning, ni rien). C'est surtout ça que je lui reproche : un problème de cohérence (et le message du compilateur).

Le message du compilateur est effectivement étrange. Cela dit, il faut bien comprendre que Rust est un langage jeune, et qu'écrire des messages censés à partir d'erreurs de type, ce n'est pas un problème si évident. Techniquement, ça plante parce que tu lui demandes d'afficher une Option avec du code pour afficher un entier, mais ici, tu voudrais qu'il réponde « attention, tu as oublié de vérifier que le pop avait bien réussi ». Le message d'erreur peu clair est très certainement dû au fait que pour l'instant, les concepteurs et développeurs du langage se sont concentrés sur des points qu'ils jugeaient plus important.

Pour la cohérence, c'est effectivement une question de compromis. Plus haut, tu as reproché au langaeg d'être « casse-couilles ». Est-ce que tu voudrais vraiment d'une écriture tab[index] qui te renvoie un Option<T> que tu es obligé de déconstruire à chaque utilisation ? Probablement pas. Pourtant, c'est aussi un « appel » qui peut échouer, tout comme pop, et on pourrait vouloir que, comme pop, cet échec potentiel soit inscrit dans le type de la fonction. Ça permet au typeur de s'assurer que tu as bien traité le cas où ça échouait à la compilation, c'est-à-dire avant même de t'avoir donné la possibilité d'exécuter un programme qui échoue. C'est ça, la sûreté dont tu parles : il ne s'agit pas d'avoir un compilateur qui écrit tout seul pour toi des programmes qui fonctionnent, mais d'avoir un compilateur qui vérifie que tu ne peux pas écrire de programme qui plante. Et oui, écrire du code robuste, ça demande un travail auquel on n'est pas habitué quand on utilise des langages peu sûrs qui te laissent faire n'importe quoi et plantent ensuite à l'exécution. C'est une question de choix.

Pour en revenir donc à cet indice, on pourrait effectivement avoir une notation qui utilise le type Option, pour typer le fait qu'elle peut échouer. Mais ce n'est pas un choix de conception absurde de se dire que, parfois, il vaut mieux avoir la possibilité d'écrire du code plus court, et c'est le cas ici. Peut-être qu'il existe une fonction Array.get qui renvoie une option, au lieu de cette notation. On pourrait aussi avoir un système de type qui vérifie lui-même que l'indice est valide - mais encore une fois, on ne peut pas avoir le beurre et l'argent du beurre, et ça demande souvent du code plus compliqué (qui contient notamment une preuve que oui, à cet endroit du code, i ne peut pas être en dehors des bornes du tableau).

Bref : boire ou conduire, il faut choisir. Rust est un langage plus sûr que ceux avec lesquels tu as apparemment eu l'habitude de travailler avant, mais la sûreté n'est pas gratuite, et elle demande en particulier de t'empêcher d'écrire les programmes plantogènes que tu écrivais auparavant. « Casse-couille » ou cassé, c'est ton choix :-)

Personnellement, je trouve que devoir faire un match à chaque pop est lourd, mais ça se justifie.

Il existe probablement des fonctions qui font le match pour toi (notamment dans le cas où tu sais que tout va bien). Encore une fois, évite de juger trop rapidement d'un langage.

J'ai l'impression qu'ils mélangent des concepts venus du fonctionnel dans un langage impératifs, tout en se positionnant en remplacement du C++ (ou du Python).

C'est le but : offrir un langage de programmation système avec un système de types censé qui apporte une sûreté très largement absente du C++ ou du C. Il se trouve qu'effectivement, ces systèmes de types ont été à l'origine développés sur une base de langages fonctionnels. Ce n'est donc pas étonnant d'y trouver une ressemblance. Mais il y a aussi des points qui diffèrent : par exemple, à une époque, Rust ne supportait pas (et n'avait pas prévu, à ma connaissance et à court terme, de supporter) l'optimisation de la récursion terminale.

+6 -0

Je n'édite pas parce que ça n'a pas grand chose à voir, mais voilà un petit bout de code rigolo en OCaml. Ça ne rentre pas exactement dans le code du sujet, mais pour ceux qui sont un peu à l'aise avec le langage, ça vaut le coût de regarder à quoi ça ressemble et de comprendre comment ça marche. Attention, si vous ne connaissez pas du tout le langage, ce n'est pas vraiment un code très adapté pour vous faire une idée : dans la vraie vie, on écrit rarement ce genre de choses.

Donc, c'est une implémentation d'un petit langage à pile qui ressemble au premier exercice. J'ai choisi de travailler avec des entiers, mais on pourrait faire exactement la même chose avec des flottants. Contrairement au premier exercice, pour ajouter un entier à la pile, il faut écrire push 5, et pour additionner les deux nombres en tête de pile, il faut écrire add (pareil pour les autres opérations). Il y a aussi une opération pop qui dépile la première valeur et une opération dup qui la duplique en tête de pile, parce que pourquoi pas, et une opération doit commencer par start et finir par finish.

Ce qui est rigolo, c'est que les opérations sont du code OCaml valide : on écrit let x = start push 1 push 2 add finish, et ça affecte 3 à x dans le programme OCaml. Je vous mets quelques exemples avec le code, et si vous avez des questions, n'hésitez pas. J'aimerais aussi regarder ce que ça donne avec une pile typée qui peut contenir des valeurs de type différents, par exemple des entiers et des booléens, probablement avec quelques GADT. Peut-être que si ça plaît aux foules, ce serait aussi amusant d'en profiter pour essayer un langage avec des types dépendants, comme Coq ou Idris, pour prouver des choses du genre pop . dup = id.

Bonne lecture :-)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
let start f =
  f []

let push stack n f =
  f (n :: stack)

let pop stack f = match stack with
  | [] -> failwith "pop"
  | x :: xs -> f xs

let dup stack f = match stack with
  | [] -> failwith "dup"
  | x :: xs -> f (x :: x :: xs)

let f_op ( <+> ) stack f = match stack with
  | x :: y :: zs -> f @@ (y <+> x) :: zs
  | _ -> failwith "add"

let add stack = f_op ( + ) stack
let sub stack = f_op ( - ) stack
let mul stack = f_op ( * ) stack
let div stack = f_op ( / ) stack

let finish stack = match stack with
  | [x] -> x
  | _ -> failwith "Plouf"

let x = start push 1 push 2 add push 3 dup pop add finish

(* val x : int = 6 *)

let y =
  start
    push 9
    push 9
    add
    push 5
    push 4
    push 98
    mul
    div
    sub
    finish

(* val y : int = 18 *)
+2 -0

Jugement à l'emporte pièce est très exactement le bon terme : mon message est rempli de « personnellement » et autres « à mon avis ». Je ne prétends pas avoir fait un test rigoureux du langage.

Je pense qu'un problème vient du fait que je ne soit pas la cible du langage : je programme principalement pour faire des simulations scientifiques, donc je fait du Fortran, du C1 et des scripts python.

mais ici, tu voudrais qu'il réponde « attention, tu as oublié de vérifier que le pop avait bien réussi ».

C'est l'idée.

Est-ce que tu voudrais vraiment d'une écriture tab[index] qui te renvoie un Option<T> que tu es obligé de déconstruire à chaque utilisation ?

Pourquoi pas. Je suis sérieux, c'est là une question de cohérence. Soit on fait confiance au développeur et on considère que s'il demande une connerie, le programme fait une connerie, soit le langage est stricte et ne laisse pas passer des conneries. Là, on est dans un entre deux. Dans l'idée, ça signifierait que tout élément extrait d'un itérable est une Option. C'est générique, simple (même si lourd) et cohérent, donc oui, je préférerais.

Comme je le reconnais, la sûreté, ça se justifie. Laisser des contournements aussi immédiats me semble contradictoire.

L'autre problème dont tu ne parles pas et qui m'a bien frustré, c'est la doc. La doc de pop dit

Removes the last element from a vector and returns it, or None if it is empty.

Je croyais en lisant ça qu'il retournant soit un élément du vecteur, soit None (comme fait python pour certaines fonctions, par exemple). Sauf qu'en fait, il retourne un truc qu'il faudra évaluer. J'ai complètement mésinterprété la doc, mais avec mon bagage, c'était couru d'avance.

C'est le but : offrir un langage de programmation système avec un système de types censé qui apporte une sûreté très largement absente du C++ ou du C. Il se trouve qu'effectivement, ces systèmes de types ont été à l'origine développés sur une base de langages fonctionnels.

Donc je fais pleinement partie du public cible du langage. J'ai dit le contraire plus haut, donc je vais un peu développer. Mon usage typique, c'est d'envoyer une simulation qui va tourner pendant 3 jours. Si ça plante, je dois tout recommencer. Le langage doit donc aider à la fiabilité tout en étant rapide. S'il aide à la parallélisation, c'est gagné, je prends. ^^ Sauf que. Sauf que mes profs/collègues trouvent que l'excellente doc de numpy est horriblement compliqué et qu'on ne trouve jamais ce qu'on cherche dedans. On m'a parlé de Rust comme d'un truc qui a pour but de remplacer le C++. Mais comment diables voulez-vous que je propose ça à mes collègues ? Théoriquement, c'est génial, mais la marche à franchir est dantesque. La doc, en ne mettant pas le système de vérification dans les exemples, le compilateur en sortant des messages long et pas super clairs n'aident en rien.

Je suis frustré, disons-le. :D

Aparté : je ne suis pas informaticien. Mes collègues non plus. Ce n'est pas le cœur de métier, même si c'est massivement utilisé. Quand je dis que mes collègues sont mauvais en programmation, c'est un constat et non une critique : il va falloir faire avec ce niveau en informatique. Quand aux jeunes que j'ai côtoyer, c'est kif-kif.

Aparté bis : l'un des trois points mis en avant par Rust est le "concurency". S'il aide vraiment à la parallélisation, les opportunités d'un point de vue simulations sont vraiment importantes. D'où une plus grande frustration encore. J'hésite vraiment entre inutilisable pour moi et super utile. Et je n'exclue pas que ce soit les deux. :-°


  1. Du mauvais C, disons le. Ma seule excuse, c'est que je pars de code pire encore. Moi, je découpe mon code en fonction avant d'atteindre les 1000 lignes, et je ne tire pas 100 milliards de nombre aléatoire en utilisant le rand par défaut. Oui, j'ai vraiment vu ça. 

+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