F# (prononcé F dièse ) est un langage de programmation multi-paradigme , à usage général , de haut niveau , fortement typé , qui englobe les méthodes de programmation fonctionnelle , impérative et orientée objet . Il est le plus souvent utilisé comme langage d'infrastructure de langage commun (CLI) multiplateforme sur .NET , mais peut également générer du code JavaScript et du code d'unité de traitement graphique (GPU).
F# est développé par la F# Software Foundation , Microsoft et des contributeurs open source. Un compilateur open source multiplateforme pour F# est disponible auprès de la F# Software Foundation. F# est un langage entièrement pris en charge dans Visual Studio et JetBrains Rider . Des plug-ins prenant en charge F# existent pour de nombreux éditeurs largement utilisés, notamment Visual Studio Code , Vim et Emacs .
F# est un membre de la famille des langages ML et est à l'origine une implémentation .NET Framework d'un cœur du langage de programmation OCaml . Il a également été influencé par C# , Python , Haskell , Scala et Erlang .
Histoire
Versions
L'évolution du langage
F# utilise un processus de développement et d'ingénierie ouvert. Le processus d'évolution du langage est géré par Don Syme de Microsoft Research en tant que dictateur bienveillant à vie (BDFL) pour la conception du langage, en collaboration avec la F# Software Foundation. Les versions antérieures du langage F# ont été conçues par Microsoft et Microsoft Research à l'aide d'un processus de développement fermé.
F# a été inclus pour la première fois dans Visual Studio dans l' édition 2010 , au même niveau que Visual Basic (.NET) et C# (bien qu'en option), et reste dans toutes les éditions ultérieures, rendant ainsi le langage largement disponible et bien pris en charge.
F# est originaire de Microsoft Research, Cambridge, Royaume-Uni. Le langage a été conçu et implémenté à l'origine par Don Syme , selon qui, dans l'équipe fsharp, on dit que le F signifie « Fun ». Andrew Kennedy a contribué à la conception des unités de mesure . Les outils Visual F# pour Visual Studio sont développés par Microsoft. La F# Software Foundation a développé le compilateur et les outils open source F#, en intégrant l'implémentation du compilateur open source fournie par l'équipe Microsoft Visual F# Tools.
Aperçu de la langue
Programmation fonctionnelle
F# est un langage fonctionnel fortement typé doté d'un grand nombre de fonctionnalités que l'on trouve normalement uniquement dans les langages de programmation fonctionnels , tout en prenant en charge les fonctionnalités orientées objet disponibles dans C#. Ensemble, ces fonctionnalités permettent d'écrire des programmes F# dans un style entièrement fonctionnel et permettent également de mélanger les styles fonctionnel et orienté objet.
Voici quelques exemples de caractéristiques fonctionnelles :
- Tout est une expression
- Inférence de type (en utilisant l'inférence de type Hindley-Milner )
- Fonctionne comme un citoyen de première classe
- Fonctions anonymes avec sémantique de capture (c'est-à-dire fermetures )
- Variables et objets immuables
- Prise en charge de l'évaluation paresseuse
- Fonctions d'ordre supérieur
- Fonctions imbriquées
- Curry
- Correspondance de motifs
- Types de données algébriques
- Tuples
- Compréhension de liste
- Prise en charge du modèle Monad (appelé expressions de calcul )
- Optimisation des appels de queue
F# est un langage basé sur des expressions utilisant une évaluation rapide et également dans certains cas une évaluation paresseuse . Chaque instruction en F#, y compris ifles expressions, tryles expressions et les boucles, est une expression composable avec un type statique. Les fonctions et les expressions qui ne renvoient aucune valeur ont un type de retour de unit. F# utilise le letmot-clé pour lier des valeurs à un nom. Par exemple :
soit x = 3 + 4
lie la valeur 7au nom x.
Les nouveaux types sont définis à l'aide typedu mot-clé. Pour la programmation fonctionnelle, F# fournit des types tuple , record , discriminated union , list , option et result . Un tuple représente un ensemble de n valeurs, où n ≥ 0. La valeur n est appelée l' arité du tuple. Un 3-tuple serait représenté par (A, B, C), où A, B et C sont des valeurs de types éventuellement différents. Un tuple peut être utilisé pour stocker des valeurs uniquement lorsque le nombre de valeurs est connu au moment de la conception et reste constant pendant l'exécution.
Un enregistrement est un type dans lequel les membres de données sont nommés. Voici un exemple de définition d'enregistrement :
type R = { Nom : chaîne Âge : int }
Les enregistrements peuvent être créés sous la forme }. Le mot clé est utilisé pour créer une copie d'un enregistrement, comme dans }, qui crée un nouvel enregistrement en copiant et en modifiant la valeur du champ (en supposant que l'enregistrement créé dans le dernier exemple était nommé ). letr={Name="AB";Age=42with{rwithName="CD"rNamer
Un type d'union discriminé est une version sûre des unions C. Par exemple,
type A = | UnionCaseX de chaîne | UnionCaseY de int
Les valeurs du type union peuvent correspondre à l'un ou l'autre des cas d'union. Les types des valeurs portées par chaque cas d'union sont inclus dans la définition de chaque cas.
Le type de liste est une liste chaînée immuable représentée soit à l'aide d'une notation ( est l' opérateur cons ) soit d'un raccourci comme . Une liste vide s'écrit . Le type d'option est un type d'union discriminé avec des choix ou . Les types F# peuvent être génériques , implémentés comme des types .NET génériques. head::tail::[item1;item2;item3][]Some(x)None
F# prend en charge les fonctions lambda et les fermetures . Toutes les fonctions de F# sont des valeurs de première classe et sont immuables. Les fonctions peuvent être curryfiées . Étant des valeurs de première classe, les fonctions peuvent être passées comme arguments à d'autres fonctions. Comme d'autres langages de programmation fonctionnelle, F# permet la composition de fonctions à l'aide des opérateurs >>et <<.
F# fournitexpressions de séquencequi définissent une séquenceseq { ... }, une liste[ ... ]ou un tableau[| ... |]à l'aide d'un code qui génère des valeurs. Par exemple,
seq { pour b dans 0 .. 25 faire si b < 15 alors donner b * b }
forme une séquence de carrés de nombres de 0 à 14 en filtrant les nombres de la plage de nombres de 0 à 25. Les séquences sont des générateurs – les valeurs sont générées à la demande (c'est-à-dire qu'elles sont évaluées paresseusement ) – tandis que les listes et les tableaux sont évalués avec empressement.
F# utilise la correspondance de motifs pour lier des valeurs à des noms. La correspondance de motifs est également utilisée lors de l'accès à des unions discriminées : l'union est une valeur mise en correspondance avec des règles de motif et une règle est sélectionnée lorsqu'une correspondance réussit. F# prend également en charge les motifs actifs comme une forme de correspondance de motifs extensible. Elle est utilisée, par exemple, lorsque plusieurs façons de faire correspondre un type existent.
F# prend en charge une syntaxe générale pour définir des calculs de composition appelésexpressions de calcul . Les expressions de séquence, les calculs asynchrones et les requêtes sont des types particuliers d'expressions de calcul. Les expressions de calcul sont une implémentation dumodèlemonade
Programmation impérative
La prise en charge de F# pour la programmation impérative inclut
forboucleswhileboucles- tableaux , créés avec la
[| ... |]syntaxe - table de hachage , créée avec la
dict [ ... ]syntaxe ouSystem.Collections.Generic.Dictionary<_,_>le type.
Les valeurs et les champs d'enregistrement peuvent également être étiquetés comme mutable. Par exemple :
// Définir 'x' avec la valeur initiale '1' soit mutable x = 1 // Changer la valeur de 'x' en '3' x <- 3
De plus, F# prend en charge l’accès à tous les types et objets CLI tels que ceux définis dans l’ System.Collections.Genericespace de noms définissant les structures de données impératives.
Programmation orientée objet
Comme d'autres langages Common Language Infrastructure (CLI), F# peut utiliser les types CLI via la programmation orientée objet. La prise en charge de F# pour la programmation orientée objet dans les expressions comprend :
- Notation par points, par exemple,
x.Name - Expressions d'objet, par exemple, }
{newobj()withmemberx.ToString()="hello" - Construction d'objet, par exemple,
newForm() - Tests de type, par exemple,
x:?string - Coercitions de type, par exemple,
x:?>string - Arguments nommés, par exemple,
x.Method(someArgument=1) - Setters nommés, par exemple,
newForm(Text="Hello") - Arguments facultatifs, par exemple,
x.Method(OptionalArgument=1)
La prise en charge de la programmation orientée objet dans les modèles inclut
- Tests de type, par exemple,
:?stringass - Modèles actifs, qui peuvent être définis sur des types d'objets
Les définitions de type d'objet F# peuvent être des définitions de type classe, structure, interface, énumération ou délégué, correspondant aux formes de définition trouvées dans C# . Par exemple, voici une classe avec un constructeur prenant un nom et un âge, et déclarant deux propriétés.
/// Une définition de type d' objet simple type Person ( name : string , age : int ) = member x.Name = name member x.Age = age
Programmation asynchrone
F# prend en charge la programmation asynchrone via des flux de travail asynchrones . Un flux de travail asynchrone est défini comme une séquence de commandes à l'intérieur d'un async{ ... }, comme dans
laissez asynctask = async { laissez req = WebRequest.Create ( url ) laissez ! response = req.GetResponseAsync ( ) utilisez stream = response.GetResponseStream ( ) utilisez streamreader = new System.IO.StreamReader ( stream ) retournez streamreader.ReadToEnd ( ) }
Cela let!indique que l'expression de droite (obtenir la réponse) doit être effectuée de manière asynchrone mais que le flux ne doit continuer que lorsque le résultat est disponible. En d'autres termes, du point de vue du bloc de code, c'est comme si l'obtention de la réponse était un appel bloquant, alors que du point de vue du système, le thread ne sera pas bloqué et pourra être utilisé pour traiter d'autres flux jusqu'à ce que le résultat nécessaire à celui-ci soit disponible.
Le bloc asynchrone peut être invoqué à l'aide de la Async.RunSynchronouslyfonction. Plusieurs blocs asynchrones peuvent être exécutés en parallèle à l'aide de la Async.Parallelfonction qui prend une liste d' asyncobjets (dans l'exemple, asynctaskest un objet asynchrone) et crée un autre objet asynchrone pour exécuter les tâches des listes en parallèle. L'objet résultant est invoqué à l'aide de Async.RunSynchronously.
L'inversion de contrôle en F# suit ce modèle.
Depuis la version 6.0, F# prend en charge la création, la consommation et le renvoi direct de tâches .NET.
ouvrir System.Net.Http laisser fetchUrlAsync ( url : string ) = // chaîne - > Tâche< chaîne > tâche { utiliser client = nouveau HttpClient ( ) let ! response = client.GetAsync ( url ) let ! content = response.Content.ReadAsStringAsync ( ) faire ! Tâche.Delay 500 renvoyer le contenu } // Utilisation let fetchPrint () = let task = task { let! data = fetchUrlAsync "https://example.com" printfn $ "{data}" } task . Wait ()
Programmation parallèle
La programmation parallèle est prise en charge en partie via les opérations Async.Parallel, Async.Startet d'autres opérations qui exécutent des blocs asynchrones en parallèle.
La programmation parallèle est également prise en charge via les Array.Parallelopérateurs de programmation fonctionnelle de la bibliothèque standard F#, l'utilisation directe du System.Threading.Tasksmodèle de programmation des tâches, l'utilisation directe du pool de threads .NET et des threads .NET et via la traduction dynamique du code F# vers des moteurs d'exécution parallèles alternatifs tels que le code GPU .
Unités de mesure
Le système de type F# prend en charge la vérification des unités de mesure pour les nombres.
En F#, vous pouvez affecter des unités de mesure, telles que des mètres ou des kilogrammes, à des valeurs à virgule flottante, à des entiers non signés et à des entiers signés. Cela permet au compilateur de vérifier que l'arithmétique impliquant ces valeurs est cohérente sur le plan des dimensions, ce qui contribue à éviter les erreurs de programmation courantes en garantissant, par exemple, que les longueurs ne sont pas ajoutées par erreur aux temps.
La fonctionnalité d'unités de mesure s'intègre à l'inférence de type F# pour nécessiter un minimum d'annotations de type dans le code utilisateur.
[< Mesure >] type m // mètre [< Mesure >] type s // seconde soit la distance = 100 . 0 < m > // float<m> soit le temps = 5 . 0 < s > // float<s> soit la vitesse = distance / temps // float<m/s> [< Mesure >] type kg // kilogramme [< Mesure >] type N = ( kg * m )/( s ^ 2 ) // Newtons [< Mesure >] type Pa = N /( m ^ 2 ) // Pascals [< Mesure >] type jours let better_age = 3u < jours > // uint<jours>
Le vérificateur de type statique de F# fournit cette fonctionnalité au moment de la compilation, mais les unités sont effacées du code compilé. Par conséquent, il n'est pas possible de déterminer l'unité d'une valeur au moment de l'exécution.
Métaprogrammation
F# permet certaines formes de personnalisation de la syntaxe via la métaprogrammation pour prendre en charge l'intégration de langages spécifiques à un domaine personnalisé dans le langage F#, notamment via des expressions de calcul.
F# inclut une fonctionnalité de métaprogrammation d'exécution appelée citations. Une expression de citation s'évalue en une représentation arborescente de syntaxe abstraite des expressions F#. De même, les définitions étiquetées avec l' [<ReflectedDefinition>]attribut sont également accessibles sous leur forme de citation. Les citations F# sont utilisées à diverses fins, notamment pour compiler du code F# en code JavaScript et GPU . Les citations représentent leurs expressions de code F# sous forme de données destinées à être utilisées par d'autres parties du programme tout en exigeant qu'il s'agisse de code F# syntaxiquement correct.
Une programmation riche en informations
F# 3.0 a introduit une forme de métaprogrammation au moment de la compilation via la génération de types statiquement extensibles appelée fournisseurs de types F#. Les fournisseurs de types F# permettent d'étendre le compilateur et les outils F# avec des composants qui fournissent des informations de type au compilateur à la demande au moment de la compilation. Les fournisseurs de types F# ont été utilisés pour donner un accès fortement typé aux sources d'informations connectées de manière évolutive, y compris au graphe de connaissances Freebase .
Dans F# 3.0, les fonctionnalités de citation et d'expression de calcul de F# sont combinées pour implémenter des requêtes LINQ . Par exemple :
// Utilisez le fournisseur de type OData pour créer des types qui peuvent être utilisés pour accéder à la base de données Northwind. open Microsoft.FSharp.Data.TypeProviders tapez Northwind = ODataService < "http://services.odata.org/Northwind/Northwind.svc" > laissez db = Northwind . GetDataContext () // Une expression de requête. laissez query1 = query { pour le client dans la base de données . Les clients sélectionnent le client }
La combinaison de fournisseurs de types, de requêtes et de programmation fonctionnelle fortement typée est connue sous le nom de programmation riche en informations .
Programmation d'agent
F# prend en charge une variante du modèle de programmation d'acteurs via l'implémentation en mémoire d'agents asynchrones légers. Par exemple, le code suivant définit un agent et publie 2 messages :
type Message = | Mise en file d' attente de chaîne | Sortie de la file d'attente de AsyncReplyChannel < Option < chaîne >> // Fournit un accès simultané à une liste de chaînes let listManager = MailboxProcessor . Start ( fun inbox -> let rec messageLoop list = async { let! msg = inbox . Receive () match msg avec | Enqueue item -> return ! messageLoop ( item :: list ) | Dequeue replyChannel -> liste de correspondance avec | [] -> replyChannel . Reply None return ! messageLoop list | head :: tail -> replyChannel . Reply ( Some head ) return ! messageLoop tail } // Démarrer la boucle avec une liste vide messageLoop [] ) // Utilisation async { // Mettre en file d'attente certaines chaînes listManager . Post ( Enqueue "Hello" ) listManager . Post ( Enqueue "World" ) // Retirer et traiter les chaînes let! str = listManager . PostAndAsyncReply ( Dequeue ) str |> Option . iter ( printfn "Dequeued: %s" ) } | > Async.Démarrer
Outils de développement
- Visual Studio , avec les outils Visual F# de Microsoft installés, peut être utilisé pour créer, exécuter et déboguer des projets F#. Les outils Visual F# incluent une console interactive de boucle de lecture-évaluation-impression (REPL) hébergée par Visual Studio qui peut exécuter du code F# au fur et à mesure de son écriture. Visual Studio pour Mac prend également entièrement en charge les projets F#.
- Visual Studio Code contient une prise en charge complète de F# via l'extension Ionide.
- F# peut être développé avec n'importe quel éditeur de texte. Un support spécifique existe dans des éditeurs tels qu'Emacs .
- JetBrains Rider est optimisé pour le développement de code F# à partir de la version 2019.1.
- LINQPad prend en charge F# depuis la version 2.x.
Comparaison des environnements de développement intégrés
Domaines d'application
F# est un langage de programmation à usage général .
Programmation Web
La pile SAFE est une pile F# de bout en bout pour développer des applications Web. Elle utilise ASP.NET Core côté serveur et Fable côté client.
Une option alternative de bout en bout pour F# est le framework WebSharper .
Développement d'applications multiplateformes
F# peut être utilisé avec les outils Visual Studio pour Xamarin pour développer des applications pour iOS et Android . La bibliothèque Fabulous offre une interface fonctionnelle plus confortable.
Programmation analytique
F# est notamment utilisé pour la programmation financière quantitative, le trading énergétique et l'optimisation de portefeuille, l'apprentissage automatique, la veille économique et les jeux sociaux sur Facebook .
Dans les années 2010, F# a été positionné comme une alternative optimisée à C# . La capacité de script de F# et sa compatibilité inter-langage avec tous les produits Microsoft l'ont rendu populaire parmi les développeurs.
Script
F# peut être utilisé comme langage de script, principalement pour les scripts de boucle de lecture-évaluation-impression (REPL) de bureau.
Communauté Open Source
La communauté open source F# comprend la F# Software Foundation et le F# Open Source Group sur GitHub . Les projets open source F# les plus populaires incluent :
- Fable, un transpileur F# vers Javascript basé sur Babel.
- Paket, un gestionnaire de packages alternatif pour .NET qui peut toujours utiliser les référentiels NuGet , mais dispose d'une gestion de version centralisée.
- FAKE, un système de construction compatible F#.
- Giraffe, un middleware orienté fonctionnellement pour ASP.NET Core .
- Suave, un serveur Web léger et une bibliothèque de développement Web.
Compatibilité
F# dispose d'un « mode de compatibilité ML » hérité qui peut compiler directement des programmes écrits dans un grand sous-ensemble d'OCaml, sans foncteurs, objets, variantes polymorphes ou autres ajouts.
Exemples
Voici quelques petits échantillons :
// Ceci est un commentaire pour un exemple de programme Hello World. printfn "Hello World!"
Une définition de type d'enregistrement. Les enregistrements sont immuables par défaut et sont comparés par égalité structurelle.
type Personne = { Prénom : chaîne Nom : chaîne Âge : int } // Création d'une instance de l'enregistrement let person = { FirstName = "John" ; LastName = "Doe" ; Age = 30 }
Une classe Person avec un constructeur prenant un nom et un âge et deux propriétés immuables.
/// Ceci est un commentaire de documentation pour une définition de type. type Person ( name : string , age : int ) = member x . Name = name member x . Age = age /// instanciation de classe let mrSmith = Person ( "Smith" , 42 )
Un exemple simple qui est souvent utilisé pour démontrer la syntaxe des langages fonctionnels est la fonction factorielle pour les entiers 32 bits non négatifs, ici montrée en F# :
/// Utilisation de l'expression de correspondance de modèle let rec factorial n = match n avec | 0 -> 1 | _ -> n * factorial ( n - 1 ) /// Pour une fonction à un seul argument, il existe un sucre syntaxique (fonction de recherche de motifs) : let rec factorial = function | 0 -> 1 | n -> n * factorial ( n - 1 ) /// Utilisation de l'opérateur fold et range let factorial n = [ 1 .. n ] |> Seq . fold (*) 1
Exemples d'itération :
/// Itération utilisant une boucle 'for' let printList lst = for x in lst do printfn $ "{x}" /// Itération utilisant une fonction d'ordre supérieur let printList2 lst = List . iter ( printfn "%d" ) lst /// Itération utilisant une fonction récursive et une correspondance de motifs let rec printList3 lst = match lst with | [] -> () | h :: t -> printfn "%d" h printList3 t
Exemples de Fibonacci :
/// Formule du nombre de Fibonacci [< TailCall >] soit fib n = soit rec g n f0 f1 = correspond à n avec | 0 -> f0 | 1 -> f1 | _ -> g ( n - 1 ) f1 ( f0 + f1 ) g n 0 1 /// Une autre approche - une séquence infinie paresseuse de nombres de Fibonacci soit fibSeq = Seq . unfold ( fun ( a , b ) -> Some ( a + b , ( b , a + b ))) ( 0 , 1 ) // Imprimer les fibs pairs [ 1 .. 10 ] |> Liste . map fib |> Liste . filter ( fun n -> ( n % 2 ) = 0 ) |> printList // Même chose, en utilisant une expression de liste [ for i in 1 .. 10 do let r = fib i if r % 2 = 0 then yield r ] |> printList
Un exemple de programme Windows Forms :
// Ouvrir la bibliothèque Windows Forms open System.Windows.Forms // Créez une fenêtre et définissez quelques propriétés let form = new Form ( Visible = true , TopMost = true , Text = "Bienvenue dans F#" ) // Créer une étiquette pour afficher du texte sous la forme let label = let x = 3 + ( 4 * 5 ) new Label ( Text = $ "{x}" ) // Ajouter l'étiquette au formulaire form . Controls . Add ( label ) // Enfin, exécutez le formulaire [< System . STAThread >] Application . Run ( form )
Exemple de programmation parallèle asynchrone (tâches CPU et E/S parallèles) :
/// Un détecteur de nombres premiers simple let isPrime ( n : int ) = let bound = int ( sqrt ( float n )) seq { 2 .. bound } |> Seq . forall ( fun x -> n % x <> 0 ) // Nous utilisons des workflows asynchrones let primeAsync n = async { return ( n , isPrime n ) } /// Renvoyer les nombres premiers entre m et n en utilisant plusieurs threads let primes m n = seq { m .. n } |> Seq . map primeAsync |> Async . Parallel |> Async . RunSynchronously |> Array . filter snd |> Array . map fst // Exécuter un test primes 1000000 1002000 |> Array . iter ( printfn "%d" )