Aller au menu - Aller au contenu

Icône Pour plus de types

Avatar
Mise à jour : 05/11/2011
Difficulté : Facile Facile Creative Commons BY-SA
973 visites depuis 7 jours, dont 46 sur ce chapitre classé 130/786
Dans ce chapitre, nous allons apprendre à manipuler un nouveau type, qui permet de regrouper plusieurs valeurs dans des "boîtes". L'intérêt ? Notamment, ce type nous permet de renvoyer plusieurs valeurs à la fois, stockées dans une seule de ces boîtes. On peut également s'en servir pour représenter des coordonnées de points en géométrie, ou pour numéroter tous les éléments d'une liste, etc.

Dans la suite, je reviendrai sur l'importance du typage en OCaml. Enfin, nous commencerons à parler de la création de types, qui permet d'écrire des programmes plus lisibles, avec des codes ayant plus de sens.
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Les tuples

Le nom est lâché : les boîtes dont je parlais en introduction s'appellent en fait des tuples (aussi appelés n-uplets). On les note en OCaml comme en mathématiques, à l'aide de parenthèses et de virgules : (1, 2) est par exemple un tuple à deux éléments (en Français on parlerait plutôt de couple ou de doublet). Cependant on peut y stocker d'autres éléments que des nombres, par exemple des listes ou, pourquoi pas, d'autres tuples. Nous reviendrons sur le typage des tuples plus loin.

Dans de nombreux langages, la fonction zip_with présentée au chapitre précédent n'existe pas. À la place, par exemple en Python, on trouve une fonction zip telle que zip [a1; a2; a3] [b1; b2; b3] = [(a1, b1); (a2, b2); (a3, b3)] : on réunit donc les deux listes en une seule, où les éléments sont regroupés deux à deux dans un tuple. Codez cette fonction zip avec la fonction zip_with ainsi qu'une fonction lambda.

Les tuples et le filtrage de motif



Pour former un tuple, nous n'avons qu'à lister les valeurs que nous souhaitons grouper, en les séparant de virgules, et à mettre le tout entre parenthèses. Mais pour lire des valeurs à l'intérieur d'un tuple, nous allons une fois de plus être confrontés au filtrage de motifs, comme on le faisait déjà avec les listes. Voici par exemple le code des fonctions fst et snd, prédéfinies en OCaml :

Code : OCaml
1
2
let fst (x, y) = x
let snd (x, y) = y


Notez que seules ces deux fonctions sont prédéfinies, et qu'elles ne marchent que sur des couples. Notez aussi un trait élégant de la forme let : le filtrage de motif intervient directement dans la déclaration des arguments, on n'a pas à utiliser la forme match with pour si peu. Quoi qu'il en soit, dans tous les autres cas vous devrez utiliser le filtrage de motifs pour pouvoir exploiter vos tuples.

Notez que le typage d'OCaml surveille la cohérence des tuples que nous pouvons récupérer : tous les tuples que nos fonctions peuvent recevoir sont de même taille. Contrairement aux listes, cependant, ils peuvent contenir de tout ; un même tuple peut par exemple contenir un nombre et un nom (alors que c'était interdit avec une liste), par exemple le pseudonyme d'un Zéro et son âge. Ici aussi, le système de typage veille : si une fonction attend en argument un tuple dont le premier élément est une chaîne, le type d'argument autorisé sera réduit aux tuples commençant par une chaîne.

Voici un exemple simple de filtrage, mais un peu moins simple qu'avant quand même :

Code : OCaml
1
2
3
4
5
6
7
# let rec concat l1 l2 = match (l1, l2) with
    ([], []) -> []
  | ([], l) -> l
  | ((x :: s), l) -> x :: concat s l ;;
val concat : 'a list -> 'a list -> 'a list = <fun>
# concat [1; 2] [3; 4] ;;
- : int list = [1; 2; 3; 4]


Vous remarquerez que la construction d'un tuple, ici dans le motif à filter, se fait intuitivement. De même il est possible de récupérer un tuple comme retour d'une fonction de deux manières possibles :

Code : OCaml
1
2
3
4
5
6
7
# let f a b = (a + b, b) ;;
val f : int -> int -> int * int = <fun>
# let a = f 2 3 ;;
val a : int * int = (5, 3)
# let (a, b) = f 2 3 ;;
val a : int = 5
val b : int = 3


Notez que la seconde est plus expressive et permet de se passer de fonctions comme fst ou snd.

Type



Le type d'un couple (entier, entier) sera int * int. On retrouve encore ici une similitude avec les mathématiques, où le couple (a, b) appartient à l'ensemble \set{N} \times \set{N} si a \in \set{N} et b \in \set{N}.

Code : OCaml
1
2
3
4
5
6
# ("coucou", 2.0) ;;
- : string * float = ("coucou", 2.)
# ([], 'c') ;;
- : 'a list * char = ([], 'c')
# (([1], 2), fst) ;;
- : (int list * int) * ('a * 'b -> 'a) = (([1], 2), <fun>)


Les parenthèses sont facultatives, à condition de bien connaître la précédence de , :

Code : OCaml
1
2
3
4
# 1, 2 :: [] ;;
- : int * int list = (1, [2])
# (1, 2) :: [] ;;
- : (int * int) list = [(1, 2)]

Le polymorphisme

Voilà qui nous amène à parler d'un point important en OCaml. Plus besoin de le répéter : OCaml est un langage qui ne plaisante pas avec les types. Ça n'est pas une question de largeur d'esprit : le typage est quelque chose de fondamental dans un programme. Dans beaucoup de langages, de nombreuses erreurs sont souvent commises par les programmes à cause d'un manque de sûreté du typage. Considérant que, de même que la gestion de la mémoire, le typage est capital pour le bon déroulement du programme, OCaml préfère s'en charger lui-même plutôt que de le laisser aux mains du programmeur.

Mais savoir comment il procède en gros pour vérifier qu'un morceau de code est correct du point de vue du typage n'est pas inintéressant. Lorsque OCaml lit un bout de code, il cherche à en déterminer, en partant de ce qu'il connaît, quelles contraintes il doit poser sur le type de ce qu'il ne connaît pas. Par exemple, let f a b = a + b est facile, car (+) est connue pour être de type int -> int -> int. a et b doivent donc être de type int, ainsi que le sera le retour de f (elle ne pourra donc pas être employée partout).

En revanche, List.hd, que nous connaissons déjà, renvoie la tête d'une liste, mais c'est tout ce que l'on peut en dire. La liste doit être non-vide, soit, mais cette information n'apparaît pas explicitement dans le type. A priori, on peut donc utiliser la fonction List.hd sur des listes de tous les types : int list, float list, int list list... C'est ce que l'on appelle le polymorphisme.

Voici un exemple plus général encore : let id x = x est la fonction identité. Elle est plus générale encore que List.hd, car elle accepte n'importe quoi (pour le renvoyer à l'identique). Son type est noté 'a -> 'a : il faut y lire "n'importe quel type".

Lorsque OCaml rencontre une situation d'indétermination, c'est à dire de polymorphisme, il note le type à l'aide d'une lettre de l'alphabet précédée d'une apostrophe. Une fonction construisant un triplet, comme let triple a b c = (a, b, c) sera de type 'a -> 'b -> 'c -> 'a * 'b * 'c car les trois éléments ne sont pas forcément tous du même type, mais que le tuple créé contiendra les types donnés en argument. Notez qu'une application partielle peut ou non déterminer le type des arguments : filter ((<) 5) est de type int list -> int list à cause du 5. En revanche, triple 5 sera de type 'a -> 'b -> int * 'a * 'b.

Le programmeur a le droit de préciser lui-même le type d'une expression à l'aide des contraintes de type, notées grâce aux deux points : . Il suffit de faire suivre l'expression des deux points, et du type de contrainte :

Code : OCaml
1
2
3
let id (x : int) = x    (* De type int -> int *)
let x = ( [] : int list)
    in 3.2 :: x           (* Provoquera une erreur *)


Mais ce système ne laisse cependant pas généraliser outre mesure les types. let add (x : 'a) (y : 'a) = a+b reste de type int -> int -> int.

Quelques remarques sur l'utilité du système de typage en OCaml

Comme je l'ai déjà dit, le typage automatique établi par OCaml permet d'éliminer des erreurs qui ne pardonneraient pas. Cette vérificaton s'effectue à la compilation, et non à l'exécution, ce qui assure donc les performances.

Mais le mécanisme de typage d'OCaml par recherche de contraintes, dit par "inférence de type", possède d'autres avantages : notamment, il est porteur de beaucoup d'informations.

Prenons par exemple la fonction List.hd. J'ai dit plus haut que son type 'a list -> 'a n'apportait aucune information au programmeur quant au fait que la liste passée en argument devait être non-vide. C'est pourtant implicite : si la fonction renvoie un élément de même type que ceux de la liste argument, il est probable que cet élément a été prélevé à la liste, non ?

Assez souvent, d'ailleurs, le nom d'une fonction et son type suffisent à la décrire, ou peu s'en faut. Par exemple, la fonction map a pour type ('a -> 'b) -> 'a list -> 'b list, ce qui est assez révélateur : elle décrit la transformation d'une liste, au moyen d'une fonction. Comme map ne connaît pas le type des données qu'elle va devoir traiter, il n'est pas possible qu'elle traite différemment une liste de string, une liste de int, etc.

Les types les plus généraux possibles sont donc donnés : map prend une fonction et une liste, et transforme chaque élément de cette liste à l'aide de la fonction. On peut donner un autre exemple : si je propose le type ('a -> bool) -> 'a list -> bool, et le nom exists, êtes-vous capables de déterminer ce que fait la fonction ?

Les types sont une partie très importante de notre programme. C'est à la fois :

  • la première partie de notre programme que nous concevons (car il est nécessaire de savoir sur quoi nous travaillons avant de décrire comment)
  • une information sur l'organisation de notre programme
  • un plan assez condensé de ce que nous allons faire : si on définit un type, certaines des opérations que l'on implémentera dessus sont évidentes (par exemple, ajouter un élément à une liste, chercher des valeurs dedans, etc. sont des opérations naturelles que l'on a envie de faire avec une liste). Donc savoir quels types vous allez utiliser suffit parfois à savoir quel code vous allez écrire : les grandes lignes, au moins, sont évidentes.


Le typage est donc un concept primordial. Il n'est pas nécessairement simple à comprendre, mais je vous conseille cette discussion sur le système de types d'OCaml si vous voulez plus de renseignements. Enfin, une petite énigme : que pensez-vous du type de fonction 'a -> 'b ? Pouvez-vous trouver une fonction de ce type là avec les concepts que nous avons déjà vus ?

Les types somme

Les types somme permettent d'énumérer les valeurs que peuvent prendre nos variables. Plus précisément, c'est une union disjointe, car chaque constructeur apporte son propre ensemble, afin d'enrichir le type. Nous introduisons le mot-clé type qui permet de créer de nouveaux types en OCaml.

Code : OCaml
1
type foo = A | B


Les variables de type foo pourront prendre les valeurs A ou B. A et B sont appelés des constructeurs de type. Les constructeurs de type doivent impérativement commencer par une majuscule.

Code : OCaml
1
2
3
4
# let a = A ;;
val a : foo = A
# let a = C ;;
Error: Unbound constructor C


Notons que A et B symbolisent les constructeurs possibles pour notre type ; dans ce cas, pensez à la similarité avec les enum de certains langages. Mais les types sommes sont beaucoup plus puissants ! Voici un autre exemple :

Code : OCaml
1
type foo = A of int | B of string


Ce type indique que si notre variable a été construite avec le constructeur A on peut lui donner, en plus, une valeur entière. Si en revanche elle a été construite avec B, on peut lui donner une valeur de type chaîne ! Exemples :

Code : OCaml
1
2
3
4
5
6
# let a = A 3 ;;
val a : foo = A 3
# let b = B "the game" ;;
val b : foo = B "the game"
# let c = B 4 ;;
Error: This expression has type int but an expression was expected of type string


Le typeur d'OCaml nous rappelle à l'ordre : il sait que le constructeur B attend une valeur de type string.

Filtrage des types somme



Les types somme se prêtent tout particulièrement bien au filtrage ! Souvenez-vous que d'une manière générale le filtrage permet d'énumérer les cas possible selon la valeur d'une variable. Dans le premier type somme que je vous ai montré un peu plus haut, on n'avait que deux valeurs possibles : A et B, elles sont donc énumérables dans un filtrage de motif :

Code : OCaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# type foo = A | B ;;
type foo = A | B

# let f a = match a with
    A -> "ananas"
  | B -> "baobab" ;;
val f : foo -> string = <fun>

# f A ;;
- : string = "ananas"


Tuples et récursivité



Revenons un petit peu sur la construction A of int. Ce qui suit le mot clé of est un type, il peut donc être un produit. La syntaxe est la même que ce que vous retourne l'interpréteur OCaml lorsque que vous lui donnez un tuple :

Code : OCaml
1
2
3
4
5
6
# type foo = A of int * int ;;
type foo = A of int * int

# let a = A 2, 3 ;;
Error: The constructor A expects 2 argument(s),
       but is applied here to 1 argument(s)


Oups ! Petit problème de précédence on dirait :

Code : OCaml
1
2
# let a = A (2, 3) ;;
val a : foo = A (2, 3)


Notez que let a = A 2, 3 est tout à fait correct, mais n'a pas la même sémantique :

Code : OCaml
1
2
3
4
# type foo = A of int | B ;;
type foo = A of int | B
# let a = A 2, 3 ;;
val a : foo * int = (A 2, 3)


Un exemple pertinent d'utilisation des tuples dans les types somme est qu'ils permettent de définir des arbres en OCaml ! En y réfléchissant, un arbre c'est soit l'arbre vide, soit un nœud étiquetté par une valeur et qui contient un fils gauche et un fils droit.

Code : OCaml
1
2
3
4
# type tree = Nil | Node of tree * int * tree ;;
type tree = Nil | Node of tree * int * tree
# let t = Node (Node (Nil, 4, Nil), 1, Node (Nil, 3, Nil)) ;;
val t : tree = Node (Node (Nil, 4, Nil), 1, Node (Nil, 3, Nil))


Les types OCaml sont récursifs par défaut, contrairement aux fonctions ! Pas besoin du mot clé rec, je peux directement utiliser mon type dans mon type, donc.

Malheureusement, mon type d'arbre souffre d'un gros problème : j'ai été obligé de préciser le type des étiquettes (entier), celui-ci est donc figé et si je veux construire un arbre avec des flottants, je vais être obligé soit de créer un autre type d'arbre... soit d'utiliser le polymorphisme !

Polymorphisme sur les types somme



Souvenez-vous des listes en OCaml. Codons par exemple la fonction qui retourne la tête d'une liste :

Code : OCaml
1
2
# let h = function [] -> failwith "blbl" | x :: s -> x ;;
val h : 'a list -> 'a = <fun>


Notre fonction travaille sur des listes polymorphes, c'est-à-dire qu'on ne connait pas le type des éléments de notre liste, on sait juste que c'est une liste. OCaml précise bien que le type est polymorphe en le notant 'a. On voudrait pouvoir faire la même chose avec nos types somme. Essayons de créer un type de liste :

Code : OCaml
1
type maliste = Nil | Elem of int * maliste


Une liste, c'est soit une liste vide, soit un élément suivi d'une liste. Le problème c'est le int, on voudrait que ce soit 'a. Remplacer bêtement int par 'a ne marchera pas. Pourquoi ? Car le type de notre liste serait toujours "liste", mais celui-ci pourrait contenir des valeurs... de différents types ! Voyons voir avec un exemple, en reprenant notre fonction qui retourne la tête d'une liste, mais en ajoutant 1 à celle-ci :


Code : OCaml
1
2
3
4
5
6
7
8
# type maliste = Nil | Elem of 'a * maliste ;;
???
# let h = function Nil -> failwith "blbl" | Elem (x, s) -> x + 1 ;;
val h : maliste -> int = <fun>
# let l = Elem ("coucou", Nil) ;;
val l : maliste = Elem (1, Nil)
# h l ;;
???


Problem? Ici l est bien de type maliste, l'application (h l) est donc correctement typée, et d'après le type de h, devrait nous retourner un entier. Pourtant, on va se retrouver à additionner une chaîne avec un entier, et ça c'est pas correct !

Le problème réside dans le fait qu'il est nécessaire de paramétrer notre type ; de même que les listes d'entiers en OCaml sont de type int list, notre liste devrait être de type int maliste :

Code : OCaml
1
type 'a maliste = Nil | Elem of 'a * 'a maliste


Ici 'a s'appelle le paramètre de type. Notre liste maliste est paramétrée par un type polymorphe 'a. Voyons ce que nous donne l'interpréteur si on lui redonne notre code maintenant :

Code : OCaml
1
2
3
4
5
6
# let h = function Nil -> failwith "blbl" | Elem (x, s) -> x + 1 ;;
val h : int maliste -> int = <fun>
# let l = Elem ("coucou", Nil) ;;
val l : string maliste = Elem ("coucou", Nil)
# h l ;;
Error: This expression has type string maliste but an expression was expected of type int maliste


Et cette fois-ci on obtient bien une erreur de typage à l'application de h à l. Ouf.

Je vous laisse en exercice la création d'un type d'arbres polymorphes, et en bonus, codez un la-profondeur-d'abord-recherche (plus connu sous le nom de DFS).
Vous connaissez maintenant les rudiments fonctionnels du langage OCaml. Nous avons vu que c'était un langage qui s'appuyait sur le concept d'expression pour décrire des calculs, et que ces expressions devaient tout le temps avoir un type, même si celui-ci pouvait être déterminé automatiquement.

Mais pour construire des types plus complexes, nous avons encore des choses à apprendre !
Chapitre précédent Sommaire Chapitre suivant

Partager

6 commentaires pour "Pour plus de types"
Note moyenne : 3.77 / 4 (22 votes)
Pseudo Commentaire
Hors ligne spider-mario # Posté le 06/11/2011 à 18:08:18
Avatar

Avis : Très bon

Ville : Montigny-lès-cormeilles
Pays : France métropolitaine
Études : INSA Rouen

Autre solution possible :

Secret (cliquez pour afficher)
Obj.magic :-°
Hors ligne AporieDramatique # Posté le 06/11/2011 à 19:07:34
Groupe : Aigris++.
Groupe : Bannis

La solution de neowillow est acceptable. Pas la tienne : Obj.magic ne peut pas être codé en OCaml, c'est « de la triche ». ;)

 
Hors ligne Maëlan # Posté le 01/02/2012 à 19:23:00
Avatar

Avis : Bon

Citation : Cours
Pouvez-vous trouver une fonction de ce type là ['a -> 'b] avec les concepts que nous avons déjà vus ?

N’est-ce pas impossible ? J’aurais eu tendance à dire que 'b pourrait soit être déterminé statiquement d’après l’expression de la fonction, soit s’exprimer en fonction de 'a (par exemple 'a list). Une fonction qui prend n’importe quel type en argument et renvoie n’importe lequel sans moyen de déterminer lequel, ça me paraît louche…

Citation : neowillow
Secret (cliquez pour afficher)
Code : OCaml
1
let rec f x = f x;;

:-°

Je ne comprend pas ce code. Il s’agit d’une récursion infinie, non ? Mais alors, quel est le type de la valeur renvoyée par f ?

Par ailleurs, j’ai l’impression qu’une erreur s’est glissée dans ce code :Code : OCaml
1
2
3
4
5
6
7
8
# type maliste = Nil | Elem of 'a * maliste ;;
???
# let h = function Nil -> failwith "blbl" | Elem (x, s) -> x + 1 ;;
val h : maliste -> int = <fun>
# let l = Elem ("coucou", Nil) ;;
val l : maliste = Elem (1, Nil)    (* ce ne serait pas "coucou" au lieu de 1 ? *)
# h l ;;
???



Enfin, une remarque générale sur le cours : je n’ai vraiment pas l’impression qu’il s’adresse à des débutants. Il est lacunaire. Il y a souvent des choses qui sont utilisées sans avoir été enseignées ou expliquées avant ; quelqu’un qui a déjà un peu d’expérience en programmation peut comprendre (on est dans le domaine de l’implicite et de la devinette, avec la marge d’erreur que cela comporte), mais pas un débutant.
Par exemple, nulle part les commentaires ne sont expliqués. Également, d’un coup il y a des codes avec du texte (type string) alors que ce type n’a pas été décrit. Enfin, exemple le plus frappant pour moi :Code : OCaml
1
let h = function [] -> failwith "blbl" | x :: s -> x ;;
Hop ! D’un coup on utilise la syntaxe function et en même temps failwith, aucun des deux n’ayant été expliqué avant (ni explicité après). Alors, je crois deviner la signification de la première (une sorte de match with) et de la deuxième (une fonction qui lancerait une exception ?), mais rien n'est sûr. Et je ne pense pas que tout le monde puisse parvenir à ces conclusions.

Citation : Cours — Début de la partie sur les types somme
Plus précisément, c'est une union disjointe, car chaque constructeur apporte son propre ensemble, afin d'enrichir le type.
Gné ? Le mot « constructeur » est introduit seulement après cette phrase… Et qu’est-ce qu’une « union disjointe », l’« ensemble » d’un constructeur ?


Mis à part ceci, très bon big-tuto pour l’instant.

© Message rédigé sous la seule propriété intellectuelle de son auteur Maëlan, et protégé contre toute reproduction partielle ou totale par l’ACTA (je vous remercie).
Logo de Xubuntu

Ne pas passer la main à travers l’enclos des loups.
Ne pas nourrir les lamas.
Ne pas utiliser de flash pour photographier les poneys.
Ne pas se moquer des manchots.


Un alias bien pratique : alias Taurre='cat http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf | grep TOPIC'. À ceux qui comprendront. ;)
 
Hors ligne PatJ # Posté le 02/02/2012 à 10:16:36
Ça ne marchera jamais !
Avatar

Ville : Cachan
Pays : France métropolitaine
Études : ENS Cachan

Citation : Maëlan
Citation : Cours
Pouvez-vous trouver une fonction de ce type là ['a -> 'b] avec les concepts que nous avons déjà vus ?

N’est-ce pas impossible ? J’aurais eu tendance à dire que 'b pourrait soit être déterminé statiquement d’après l’expression de la fonction, soit s’exprimer en fonction de 'a (par exemple 'a list). Une fonction qui prend n’importe quel type en argument et renvoie n’importe lequel sans moyen de déterminer lequel, ça me paraît louche…


C'est pourtant possible !
En fait, cela l'est en général lorsque le typeur peut déterminer statiquement que la fonction ne renverra aucune valeur.
Comme on l'a par exemple ici avec let rec f x = f x;;

Ce code est effectivement une récursion infinie. Le typeur comprend ceci:
- L'argument en entrée x peut être n'importe quoi, disons 'a.
- La valeur de retour n'existe pas. Utiliser f plus tard n'apporte donc aucune information de typage. C'est donc n'importe quelle valeur 'b.

Par exemple, voici un bout de code qui devrait t'aider à comprendre l'idée avec une autre fonction 'a -> 'b :

Code : OCaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# let f x = failwith "echec";;
val f : 'a -> 'b = <fun>
# let g x = x+1;;
val g : int -> int = <fun>
# let h x = x^" au revoir !";;
val h : string -> string = <fun>
# try g (f 1.0) with _ -> 1;;
- : int = 1
# try h (f (3,'x')) with _ -> "cool";;
- : string = "cool"


Ici, f ne peut que lever une exception. Sa valeur de retour "normale" est donc inutile. On peut alors utiliser f n'importe où, que ce soit dans g ou h. Sa valeur de retour est donc forcément 'b.

Citation : Maëlan

Par ailleurs, j’ai l’impression qu’une erreur s’est glissée dans ce code :Secret (cliquez pour afficher)
Code : OCaml
1
2
3
4
5
6
7
8
# type maliste = Nil | Elem of 'a * maliste ;;
???
# let h = function Nil -> failwith "blbl" | Elem (x, s) -> x + 1 ;;
val h : maliste -> int = <fun>
# let l = Elem ("coucou", Nil) ;;
val l : maliste = Elem (1, Nil)    (* ce ne serait pas "coucou" au lieu de 1 ? *)
# h l ;;
???

Effectivement, après, ce code a pour but de montrer qu'il est impossible pour le compilateur de déterminer ici si on lui donne un entier ou une chaîne de caractère.

Citation : Maëlan
Enfin, une remarque générale sur le cours : je n’ai vraiment pas l’impression qu’il s’adresse à des débutants. Il est lacunaire.

Effectivement. Je viens de m'y attabler et j'essaye de résoudre toutes les lacunes et imprécisions que tu décris ici. Merci beaucoup pour tes remarques !

Message publié sous licence CC-BY-SA
 
Hors ligne Maëlan # Posté le 02/02/2012 à 18:33:03
Avatar

Avis : Bon

Merci pour tes explications !


Une dernière remarque que j’avais oublié : l’alternance des styles de zones de codes (console en blanc sur noir et code OCaml avec coloration syntaxique) est gênante. Je pense que le style « console » était utilisé pour le dialogue avec l’interpréteur (donc avec ses messages de retour et des # devant les lignes) et le style « coloré » pour le code source « brut », mais en pratique ce n’est pas toujours le cas et c’est de toute façon troublant de passer de l’un à l’autre.
Ah oui, et ce serait bien d’expliquer comment coder dans un fichier source plutôt que directement dans l’interpréteur. Même sans parler de compilation, avec un ocaml < source.ml. Parce que l’interpréteur c’est peut-être bien au tout début, comme vous le dites, mais après je trouve plus pratique d’écrire dans un fichier (il suffit de modifier une ligne plutôt que de tout retaper si besoin, et étant donné qu’on est en phase d’apprentissage faire plein de tests est très important je trouve).

Bonne continuation et merci pour le cours !

© Message rédigé sous la seule propriété intellectuelle de son auteur Maëlan, et protégé contre toute reproduction partielle ou totale par l’ACTA (je vous remercie).
Logo de Xubuntu

Ne pas passer la main à travers l’enclos des loups.
Ne pas nourrir les lamas.
Ne pas utiliser de flash pour photographier les poneys.
Ne pas se moquer des manchots.


Un alias bien pratique : alias Taurre='cat http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf | grep TOPIC'. À ceux qui comprendront. ;)
 

Voir tous les commentaires