• [^] # Re: Jai: Language for game programmming

    Posté par (site web personnel) . En réponse à la dépêche C++17, Genèse d’une version mineure. Évalué à 8.

    Yahoo, je suis enfin reconnu par mes pairs ;)

    En effet, en C++ tous les types numériques sont implicitement castables en un autre type numérique, et comme les enums et les bools sont des types numériques, cela devient vite comique. Tu peux cependant activer des warnings quand ces conversions font de la perte d'information, mais c'est simplement un warning et surtout il dépend du compilateur :

    void add(float, int, int){}
    int main()
    {
     if(3.5)
     {
     add(2, false, 3.5);
     }
    }

    La compilation avec clang nous donne :

    λ skyskolem ~ → clang++ test_conv.cpp -Wall -Wextra -Wall -Wconversion
    test_conv.cpp:5:5: warning: implicit conversion from 'double' to 'bool' changes value from 3.5 to true
     [-Wliteral-conversion]
     if(3.5)
     ~~ ^~~
    test_conv.cpp:7:17: warning: implicit conversion from 'double' to 'int' changes value from 3.5 to 3
     [-Wliteral-conversion]
     add(2, false, 3.5);
     ~~~ ^~~
    2 warnings generated.
    

    (note: j'avoue que je n'ai pas cherché si il existe un ensemble de flags pour en trouver plus).

    À cela j'ajoute un piège dans lequel beaucoup de développeurs C++ tombent facilement c'est que par définition en C++ un constructeur à un argument est un opérateur de cast implicite :

    struct Test
    {
     Test();
     Test(int);
     Test(bool);
    };
    void foo(Test, Test, Test);
    int main()
    {
     Test a;
     foo(5, true, a);
    }

    Ce problème se règle en préfixant les constructeur du mot clé explicit, mais l'oubli est facile.

    Pour finir ce message, je voudrais expliquer comment cela fonctionne dans Haskell. C'est la même situation qu'en OCaml au sens que les conversions implicites n'existent pas. Cependant en ce qui concerne les literaux numériques c'est un peu particulier, ceux-ci sont polymorphiques, ce qui veut dire que 2 est potentiellement un Float/Double/Int/Integer/Int32/Word16 et cela ne sera décidé que par le biais de l’inférence de type, en gros c'est la fonction qui prendra notre literal en paramètre qui décidera de son sort.

    Notons cependant que ce choix est fait en fonction du literal. En gros, 3 peut être un Double, mais 3.2 ne peut pas être un Int, exemples, avec deux fonctions:

    f :: Int -> Int
    f x = 2 * x
    g :: Float -> Float
    g x = 2 * x
    Prelude> g 2
    4.0
    Prelude> f 2
    4
    Prelude> g 2.3
    4.6
    Prelude> f 2.3
    <interactive>:20:3: error:
      No instance for (Fractional Int) arising from the literal 2.3
      In the first argument of f, namely 2.3
     In the expression: f 2.3
     In an equation for it: it = f 2.3

    C'est un beau compromis dans le sens où il n'est pas nécessaire de typer explicitement ses literaux, mais aucun cast implicite n'est effectué. En contrepartie il faut de temps à autre être explicite sur le type car l'inférence de type s'y perd.

    C'est aussi pratique quand on commence à implémenter ses propres types numériques pour différents cas de figure (calcul vectoriel, auto differentiation, ...) et où il est agréable de profiter de l'écriture de litéreaux. Exemple non complet, soit une fonction f :

    f v = 2 * (v * v)

    Le type inferé par Haskell est f :: Num a => a -> a. En gros, une fonction qui prend un Num et retourne un Num.

    Cela fonctionne pour les littéraux "classiques" :

    Prelude> f 2
    8
    Prelude> f 2.3
    10.579999999999998

    Mais j'ai aussi définis mon type numérique maison Expr Float qui représente un arbre d'expression et je peux utiliser f dessus :

    > (f 5) :: Expr Float
    Mul (Constant 2.0) (Mul (Constant 5.0) (Constant 5.0))

    Bon, je me suis encore lâché et j'ai expliqué pleins de trucs au lieu de regarder le meilleur pâtissier ;) Bonne soirée.