Objective Caml - Définition

Source: Wikipédia sous licence CC-BY-SA 3.0.
La liste des auteurs de cet article est disponible ici.

Introduction

Objective Caml
OCaml.png

Apparu en 1987 (CAML), 1996 (OCaml)
Développeur INRIA
Dernière version 3.12.0 [+/−]
Paradigme multiparadigme : impérative, fonctionnelle, orientée objet
Typage Fort, statique
Dialectes JoCaml, Fresh OCaml, GCaml, MetaOCaml, OCamlDuce, OcamlP3L
Influencé par ML (langage)
A influencé F#
Système d'exploitation Multiplate-forme
Licence Q Public License (compilateur)
LGPL (bibliothèques)
Site Web http://caml.inria.fr/

Objective Caml, également connu sous sa forme abrégée OCaml, est l'implémentation la plus avancée du langage de programmation Caml, créé par Xavier Leroy, Jérôme Vouillon, Damien Doligez, Didier Rémy et leurs collaborateurs en 1996. Ce langage, de la famille des langages ML, est un projet open source dirigé et maintenu essentiellement par l'INRIA.

OCaml est le successeur de Caml Light, auquel il a ajouté entre autres une couche de programmation objet. L'acronyme CAML provient de Categorical Abstract Machine Language, un modèle de machine abstraite qui n'est cependant plus utilisé dans les versions récentes de OCaml.

Portable et performant, OCaml est utilisé dans des projets aussi divers que le logiciel de synchronisation de fichiers Unison, l'assistant de preuves formelles Coq, et des outils de vérification statique des pilotes Windows, écrits par Microsoft.

Principes

Caml est un langage fonctionnel augmenté de fonctionnalités permettant la programmation impérative. Objective Caml étend les possibilités du langage en permettant la programmation orientée objet et la programmation modulaire. Pour toutes ces raisons, OCaml entre dans la catégorie des langages multi-paradigme.

Il intègre ces différents concepts dans un système de types hérité de ML, caractérisé par un typage statique, fort et inféré.

Le système de types permet une manipulation aisée de structures de données complexes : on peut aisément représenter des types algébriques, c'est-à-dire des types hiérarchisés et potentiellement récursifs (listes, arbres…), et les manipuler aisément à l'aide du filtrage par motif. Cela fait de OCaml un langage de choix dans les domaines demandant la manipulation de structures de données complexes, par exemple les compilateurs.

Le typage fort, ainsi que l'absence de manipulation explicite de la mémoire (présence d'un ramasse-miettes) font de OCaml un langage très sûr. Il est aussi réputé pour ses performances, grâce à la présence d'un compilateur de code natif.

Principales caractéristiques

Langage fonctionnel

OCaml possède la plupart des caractéristiques communes des langages fonctionnels, en particulier des fonctions d'ordre supérieur et fermetures (closures), et un bon support de la récursion terminale.

Typage

Le typage statique d'OCaml détecte au moment de la compilation un grand nombre d'erreurs de programmation qui pourraient poser des problèmes au moment de l'exécution. Toutefois, contrairement à la plupart des autres langages, il n'est pas nécessaire de préciser le type des variables que l'on utilise. En effet, Caml dispose d'un algorithme d'inférence de types qui lui permet de déterminer le type des variables à partir du contexte dans lequel elles sont employées.

Le système de typage ML supporte le polymorphisme paramétrique, c'est-à-dire des types dont des parties seront indéterminées au moment de la définition de la valeur. Cette fonctionnalité, automatique, permet d'obtenir une généricité comparable à celle des generics de Java ou C# ou des templates de C++.

Cependant, les extensions du typage ML requises par l'intégration de fonctionnalités avancées, comme la programmation orientée objet, complexifie dans certains cas le système de types : l'utilisation de ces fonctionnalités peut alors demander un temps d'apprentissage au programmeur, qui n'est pas forcément familier des systèmes de types sophistiqués.

Filtrage

Le filtrage par motif (en anglais : pattern matching) est un élément essentiel du langage Caml. Il permet d'alléger le code grâce à une écriture plus souple que des conditions classiques, et l'exhaustivité fait l'objet d'une vérification : le compilateur propose un contre-exemple lorsqu'un filtrage incomplet est décelé. Par exemple, le code suivant est compilé mais il provoque un avertissement :

      # type etat = Actif | Inactif | Inconnu;;      # (* type somme: un état est l'une des trois valeurs: Actif, Inactif, Inconnu *)             # let est_actif = function        Actif -> true      | Inactif -> false      ;;      
        Warning P: this pattern-matching is not exhaustive.        Here is an example of a value that is not matched:        Inconnu      

Ainsi, le programme fonctionne lorsque la fonction est_actif est appelée avec un état valant Actif ou Inactif, mais s'il vaut Inconnu, la fonction renvoie l'exception Match_failure.

Modules

Les modules permettent de décomposer le programme en une hiérarchie de structures contenant des types et des valeurs logiquement reliés (par exemple, toutes les fonctions de manipulation de listes vont dans le module List). Les descendants de la famille ML sont les langages ayant actuellement les systèmes de modules les plus perfectionnés, qui permettent, en plus de disposer d'espaces de noms, de mettre en œuvre l'abstraction (valeurs accessibles dont l'implémentation est cachée) et la composabilité (valeurs qui peuvent être construites par dessus différents modules, du moment qu'ils répondent à une interface donnée).

Ainsi, les trois unités syntaxiques de construction syntaxiques sont les structures, les interfaces et les modules. Les structures contiennent l'implémentation des modules, les interfaces décrivent les valeurs qui en sont accessibles (les valeurs dont l'implémentation n'est pas exposée sont des valeurs abstraites, et celles qui n'apparaissent pas du tout dans l'implémentation du module sont inaccessibles, à l'instar des méthodes privées en programmation orientée objet). Un module peut avoir plusieurs interfaces (du moment qu'elles sont toutes compatibles avec les types de l'implémentation), et plusieurs modules peuvent vérifier une même interface. Les foncteurs sont des structures paramétrées par d'autres structures ; par exemple, les tables de hachage (module Hashtbl) de la bibliothèque standard OCaml sont utilisables comme un foncteur, qui prend en paramètre toute structure implémentant l'interface composée d'un type, d'une fonction d'égalité entre les clés, et d'une fonction de hachage.

Orienté objet

OCaml se distingue particulièrement par son extension du typage ML vers un système objet comparable à ceux utilisés par les langages objets classiques. Cela permet un sous-typage structurel, dans lequel les objets sont de types compatibles si les types de leurs méthodes sont compatibles, indépendamment de leurs arbres d'héritage respectifs. Cette fonctionnalité, que l'on peut considérer comme l'équivalent du duck typing des langages dynamiques, permet une intégration naturelle des concepts objets dans un langage globalement fonctionnel.

Ainsi, à la différence des langages orientés objet tels C++ ou Java pour lesquels toute classe définit un type, les classes OCaml définissent plutôt des abréviations de types. En effet, pour peu que le type des méthodes soient compatibles, deux objets de deux classes différentes peuvent être utilisés indifféremment dans un même contexte. Cette caractéristique de la couche objet de OCaml rompt bon nombre de principes communément admis : il est en effet possible de faire du sous-typage sans héritage, par exemple. Le côté polymorphe rompt le principe inverse. Des exemples de code, bien que rares, exhibant des cas d'héritage sans sous-typage existent également.

La force de la couche objet tient à son homogénéité et sa parfaite intégration dans la philosophie et l'esprit même du langage OCaml. Des objets fonctionnels, dont les attributs ne peuvent être modifiés et dont les méthodes, le cas échéant, en retournent une copie avec la mise-à-jour des attributs, ou encore la définition d'objets immédiats, ou à la volée, sont également possibles.

Distribution

La distribution OCaml contient :

  • Un interpréteur interactif (ocaml)
  • Un compilateur bytecode (ocamlc) et l'interpréteur de bytecode (ocamlrun)
  • Un compilateur natif (ocamlopt)
  • Des générateurs d'analyseurs lexicaux (ocamllex) et syntaxiques (ocamlyacc),
  • Un préprocesseur (camlp4), qui permet des extensions ou modifications de la syntaxe du langage
  • Un débogueur pas à pas, avec retour en arrière (ocamldebug)
  • Des outils de profiling
  • Un générateur de documentation (ocamldoc)
  • Un gestionnaire de compilation automatique (ocamlbuild), depuis OCaml 3.10,
  • Une bibliothèque standard variée

Les outils OCaml sont régulièrement utilisés sous Windows, GNU/Linux ou Mac OS, mais existent aussi sur d'autres systèmes comme les BSD.

Le compilateur bytecode permet de créer des fichiers qui sont ensuite interprétés par ocamlrun. Le bytecode étant indépendant de la plate-forme, cela assure une grande portabilité (ocamlrun pouvant a priori être compilé sur toute plate-forme supportant un compilateur C fonctionnel). Le compilateur natif produit un code assembleur spécifique à la plate-forme, ce qui sacrifie la portabilité de l'exécutable produit pour des performances grandement améliorées. Un compilateur natif est présent pour les plates-formes IA32, PowerPC, AMD64, Alpha, Sparc, Mips, IA64, HPPA et StrongArm.

Une interface de compatibilité permet de lier du code OCaml à des primitives en C, et le format des tableaux de nombre flottants est compatible avec C et Fortran. OCaml permet aussi l'intégration de code OCaml dans un programme en C, ce qui permet de distribuer des bibliothèques OCaml à des programmeurs en C sans qu'ils aient besoin de connaître ou même d'installer OCaml.

Les outils OCaml sont majoritairement codés en OCaml, à l'exception de quelques bibliothèques et de l'interpréteur bytecode, qui sont codés en C. En particulier, le compilateur natif est entièrement codé en OCaml.

Gestion de la mémoire

OCaml dispose, comme Java, d'une gestion automatisée de la mémoire, grâce à un ramasse-miettes (en anglais : garbage collector) incrémental générationnel. Celui-ci est spécialement adapté à un langage fonctionnel (optimisé pour un rythme rapide d'allocation/libération de petits objets), n'a donc pas d'impact sensible sur les performances des programmes. Il est configurable pour rester efficace dans des situations atypiques d'utilisation de la mémoire.

Performances

OCaml se distingue de la plupart des langages développés dans des milieux académiques par d'excellentes performances. En plus des optimisations locales « classiques » effectuées par le générateur de code natif, les performances profitent avantageusement de la nature fonctionnelle et statiquement et fortement typée du langage.

Ainsi, les informations de typage sont complètement déterminées à la compilation, et n'ont pas besoin d'être reproduites dans le code natif, ce qui permet entre autres de retirer complètement les tests de typage au moment de l'exécution. D'autre part, certains algorithmes de la bibliothèque standard exploitent les propriétés intéressantes des structures de données fonctionnelles pures : ainsi, l'algorithme d'union d'ensembles est asymptotiquement plus rapide que celui des langages impératifs, car il utilise leur non-mutabilité pour réutiliser une partie des ensembles de départ pour constituer l'ensemble de sortie (c'est la technique de path copying pour les structures de données persistantes).

Historiquement, les langages fonctionnels ont été considérés comme lents par certains programmeurs, car ils nécessitent naturellement la mise en œuvre de concepts (récupération de la mémoire, application partielle…) qu'on ne savait pas compiler efficacement ; les progrès des techniques de compilation ont depuis permis de rattraper l'avantage initial des langage impératifs. OCaml, en optimisant efficacement ces parties du langage et en implémentant un ramasse-miettes adapté aux allocations fréquentes des langages fonctionnels, a été un des premiers langages fonctionnels à démontrer l'efficacité retrouvée de la programmation fonctionnelle.

En général, la rapidité d'exécution est légèrement inférieure à celle d'un code équivalent en C. Xavier Leroy parle prudemment de « performances d'au moins 50 % celles d'un compilateur C raisonnable ». Ces prévisions ont depuis été confirmées par de nombreux benchmarks. En pratique, les programmes restent en général dans cette fourchette (de 1 à 2 fois celle du code C), avec des extrêmes dans les deux directions (parfois plus rapide que le C, parfois fortement ralenti par une interaction malheureuse avec le ramasse-miettes). Dans tous les cas, cela reste plus rapide que la plupart des langages récents qui ne sont pas compilés nativement, comme Python, Ruby ou même les langages de la plate-forme .NET.

En ce qui concerne la structuration du code, il dispose de modules permettant de mettre en valeur, ou d'organiser correctement l'architecture d'un programme. Il s'agit avec le typage statique d'une performance indispensable au programmeur du XXIe siècle, la rapidité d'exécution devient alors un critère secondaire.

Page générée en 0.142 seconde(s) - site hébergé chez Contabo
Ce site fait l'objet d'une déclaration à la CNIL sous le numéro de dossier 1037632
A propos - Informations légales | Partenaire: HD-Numérique
Version anglaise | Version allemande | Version espagnole | Version portugaise