Tutoriel F# : comment créer une application F# complète
Publié: 2022-03-11Ces dernières années, la programmation fonctionnelle s'est imposée comme un paradigme particulièrement rigoureux et productif. Non seulement les langages de programmation fonctionnels attirent l'attention au sein des communautés de programmeurs, mais de nombreuses grandes entreprises commencent également à utiliser des langages de programmation fonctionnels pour résoudre des problèmes commerciaux.
Par exemple, Walmart a commencé à utiliser Clojure, un dialecte Lisp fonctionnel basé sur JVM, pour son infrastructure de paiement ; Jet.com, une grande plate-forme de commerce électronique (maintenant détenue par Walmart), utilise F # pour créer la plupart de ses microservices ; et Jane Street, une société commerciale propriétaire, utilisent principalement OCaml pour créer leurs algorithmes.
Aujourd'hui, nous allons explorer la programmation F#. F # est l'un des langages de programmation fonctionnels qui est de plus en plus adopté en raison de sa flexibilité, de sa forte intégration .NET et de la haute qualité des outils disponibles. Pour les besoins de ce didacticiel F #, nous allons créer un serveur Web simple et une application mobile associée en utilisant uniquement F # pour le front-end et le back-end.
Pourquoi choisir F# et à quoi sert F# ?
Pour le projet d'aujourd'hui, nous n'utiliserons que F#. Il y a plusieurs raisons de préférer F# comme langage de prédilection :
- Intégration .NET : F # a une intégration très étroite avec le reste du monde .NET et donc un accès facile à un vaste écosystème de bibliothèques bien prises en charge et parfaitement documentées pour résoudre un large éventail de tâches de programmation.
- Concision : F# est extrêmement concis en raison de son puissant système d'inférence de type et de sa syntaxe concise. Les tâches de programmation peuvent souvent être résolues de manière beaucoup plus élégante en utilisant F# que C# ou Java. Le code F # peut sembler très simplifié en comparaison.
- Outils de développement : F# bénéficie d'une forte intégration Visual Studio, qui est l'un des meilleurs IDE pour l'écosystème .NET. Pour ceux qui travaillent sur des plates-formes non Windows, il existe une abondance de plugins dans le code Visual Studio. Ces outils rendent la programmation en F # extrêmement productive.
Je pourrais continuer sur les avantages de l'utilisation de F #, mais sans plus tarder, plongeons-y !
L'idée derrière notre didacticiel F #
Aux États-Unis, il y a un dicton populaire : « Il est cinq heures quelque part » .
Dans certaines parties du monde, 17h00 est la première heure où il est socialement acceptable de prendre un verre ou une tasse de thé traditionnelle.
Aujourd'hui, nous allons construire une application basée sur ce concept. Nous allons créer une application qui, à tout moment, recherche dans les différents fuseaux horaires, trouve où il est cinq heures et fournit cette information à l'utilisateur.
L'arrière-plan
Configuration du serveur Web
Nous allons commencer par créer le service back-end qui exécute la fonction de recherche de fuseau horaire. Nous utiliserons Suave.IO pour créer une API JSON.
Suave.IO est un framework Web intuitif avec un serveur Web léger qui permet de coder très rapidement des applications Web simples.
Pour commencer, accédez à Visual Studio et démarrez un nouveau projet d'application console F#. Si cette option ne vous est pas disponible, vous devrez peut-être installer la fonctionnalité F# avec Visual Studio Installer. Nommez le projet "FivePM". Une fois votre application créée, vous devriez voir quelque chose comme ceci :
[<EntryPoint>] let main argv = printfn "%A" argv 0 // return an integer exit codeIl s'agit d'un morceau de code de démarrage très simple qui imprime l'argument et sort avec le code d'état 0. N'hésitez pas à modifier l'instruction d'impression et à expérimenter diverses fonctions du code. Le formateur "%A" est un formateur spécial qui imprime la représentation sous forme de chaîne de n'importe quel type que vous transmettez, alors n'hésitez pas à imprimer des entiers, des flottants ou même des types complexes. Une fois que vous êtes à l'aise avec la syntaxe de base, il est temps d'installer Suave.
Le moyen le plus simple d'installer Suave consiste à utiliser le gestionnaire de packages NuGet. Accédez à Projet -> Gérer les packages NuGet, puis cliquez sur l'onglet Parcourir. Recherchez Suave et cliquez sur installer. Une fois que vous avez accepté les packages à installer, vous devriez être prêt ! Revenez maintenant à votre écran program.fs et nous sommes prêts à commencer à construire le serveur.
Pour commencer à utiliser Suave, nous devrons d'abord importer le package. En haut de votre programme, saisissez les instructions suivantes :
open Suave open Suave.Operators open Suave.Filters open Suave.SuccessfulCela importera les packages de base requis pour créer un serveur Web de base. Remplacez maintenant le code dans le main par ce qui suit, qui définit une application simple et la diffuse sur le port 8080 :
[<EntryPoint>] let main argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg app 0Le code doit avoir l'air très simple, même si vous n'êtes pas familier avec la syntaxe F # ou la manière de Suave de définir les gestionnaires d'itinéraire, le code doit être assez lisible. Essentiellement, l'application Web revient avec un statut 200 et "Hello World!" chaîne lorsqu'elle est frappée par une requête GET sur la route "/". Allez-y et exécutez l'application (F5 dans Visual Studio) et accédez à localhost:8080, et vous devriez voir "Hello World!" dans la fenêtre de votre navigateur.
Refactorisation du code serveur
Nous avons maintenant un serveur Web ! Malheureusement, il ne fait pas grand-chose - alors donnons-lui quelques fonctionnalités ! Tout d'abord, déplaçons la fonctionnalité du serveur Web ailleurs afin que nous construisions certaines fonctionnalités sans nous soucier du serveur Web (nous le connecterons au serveur Web plus tard). Définissez une fonction distincte ainsi :
// We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ path "/" >=> request (fun _ -> OK "Hello World!")] ] // Now we start the server startWebServer cfg appChangez maintenant la fonction principale comme suit et assurez-vous que nous l'avons bien fait.
[<EntryPoint>] let main argv = runWebServer argv 0Appuyez sur F5 et notre "Hello World!" le serveur devrait fonctionner comme avant.
Obtenir des fuseaux horaires
Construisons maintenant la fonctionnalité qui détermine le fuseau horaire où il est cinq heures. Nous voulons écrire du code pour parcourir tous les fuseaux horaires et déterminer le fuseau horaire le plus proche de 17h00.
De plus, nous ne voulons pas vraiment renvoyer un fuseau horaire très proche de 17h00 mais légèrement avant (par exemple 16h58) car, pour les besoins de cette démonstration, la prémisse est qu'il ne peut pas être avant 5h00. :00 pm, mais proche.
Commençons par obtenir une liste des fuseaux horaires. En F #, c'est très facile car il s'intègre si bien avec C #. Ajoutez « système ouvert » en haut et changez votre application F# en ceci :
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // Now we iterate through the list and print out the names of the timezones for tz in tzs do printfn "%s" tz.DisplayName 0Exécutez l'application et vous devriez voir dans votre console une liste de tous les fuseaux horaires, leurs décalages et leur nom d'affichage.
Création et utilisation d'un type personnalisé
Maintenant que nous avons la liste des fuseaux horaires, nous pouvons les convertir en un type de données personnalisé qui nous est plus utile, quelque chose qui contient des informations comme le décalage UTC, l'heure locale, à quelle distance il est de 17h00 en heure locale , etc. À cette fin, définissons un type personnalisé, juste au-dessus de votre fonction principale :
type TZInfo = {tzName: string; minDiff: float; localTime: string; utcOffset: float}Nous pouvons maintenant transformer les informations de fuseau horaire obtenues à la dernière étape en une liste de ces objets TZInfo. Modifiez votre fonction principale ainsi :
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] printfn "%A" tzList.Head 0Et vous devriez voir l'objet tzInfo pour l'heure standard Dateline imprimé sur votre écran.
Tri et filtrage et tuyauterie, oh mon dieu !
Maintenant que nous avons une liste de ces objets tzInfo, nous pouvons filtrer et trier ces objets pour trouver le fuseau horaire où il est 1) après 17h00 et 2) le plus proche de 17h00 des fuseaux horaires en 1). Modifiez votre fonction principale comme ceci :
[<EntryPoint>] let main argv = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain functiona calls together let closest = tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head printfn "%A" closestEt maintenant nous devrions avoir le fuseau horaire que nous recherchons.
Refactorisation du getter de fuseau horaire à sa propre fonction
Maintenant, refactorisons le code dans sa propre fonction afin de pouvoir l'utiliser plus tard. Définissez une fonction ainsi :
// the function takes uint as input, and we represent that as "()" let getClosest () = // This gets all the time zones into a List-like object let tzs = TimeZoneInfo.GetSystemTimeZones() // List comprehension + type inference allows us to easily perform conversions let tzList = [ for tz in tzs do // convert the current time to the local time zone let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz) // Get the datetime object if it was 5:00pm let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0) // Get the difference between now local time and 5:00pm local time. let minDifference = (localTz - fivePM).TotalMinutes yield { tzName=tz.StandardName; minDiff=minDifference; localTime=localTz.ToString("hh:mm tt"); utcOffset=tz.BaseUtcOffset.TotalHours; } ] // We use the pipe operator to chain function calls together tzList // filter so that we only get tz after 5pm |> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0) // sort by minDiff |> List.sortBy (fun (i:TZInfo) -> i.minDiff) // Get the first item |> List.head And our main function can just be: [<EntryPoint>] let main argv = printfn "%A" <| getClosest() 0Exécutez le code et vous devriez voir la même sortie qu'avant.

Codage JSON des données de retour
Maintenant que nous pouvons obtenir les données de fuseau horaire, nous pouvons transformer les informations en JSON et les diffuser via notre application. C'est assez simple, grâce au package JSON.NET de NewtonSoft. Revenez à votre gestionnaire de packages NuGet et recherchez Newtonsoft.Json, puis installez le package. Revenez maintenant à Program.fs et apportez une petite modification à notre fonction principale :
[<EntryPoint>] let main argv = printfn "%s" <| JsonConvert.SerializeObject(getClosest()) 0Exécutez le code maintenant et au lieu de l'objet TZInfo, vous devriez voir le JSON imprimé sur votre console.
Connexion des informations de fuseau horaire à l'API JSON
Il est très simple de le connecter à notre API JSON. Apportez simplement les modifications suivantes à votre fonction runWebServer :
// We'll use argv later :) let runWebServer argv = // Define the port where you want to serve. We'll hardcode this for now. let port = 8080 // create an app config with the port let cfg = { defaultConfig with bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]} // We'll define a single GET route at the / endpoint that returns "Hello World" let app = choose [ GET >=> choose [ // We are getting the closest time zone, converting it to JSON, then setting the MimeType path "/" >=> request (fun _ -> OK <| JsonConvert.SerializeObject(getClosest())) >=> setMimeType "application/json; charset=utf-8" ] ] // Now we start the server startWebServer cfg appExécutez l'application et accédez à localhost:8080. Vous devriez voir le JSON sur la fenêtre de votre navigateur.
Déploiement du serveur
Maintenant que nous avons le serveur d'API JSON, nous pouvons le déployer pour qu'il soit accessible sur Internet. L'un des moyens les plus simples de déployer cette application consiste à utiliser le service d'application de Microsoft Azure, qui peut être compris comme un service IIS géré. Pour déployer sur Azure App service, rendez-vous sur https://portal.azure.com et accédez à App Service. Créez une nouvelle application et accédez au centre de déploiement dans votre portail. Le portail peut être un peu écrasant si c'est votre première fois, donc si vous rencontrez des problèmes, assurez-vous de consulter l'un des nombreux didacticiels disponibles pour utiliser App Service.
Vous devriez voir une variété d'options de déploiement. Vous pouvez utiliser celui que vous aimez, mais pour des raisons de simplicité, nous pouvons utiliser l'option FTP.
Le service App recherche un fichier web.config à la racine de votre application pour savoir comment exécuter votre application. Étant donné que notre serveur Web est une application de console essentielle, nous pouvons publier l'application et l'intégrer au serveur IIS à l'aide de HttpPlatformHandler. Dans Visual Studio, ajoutez un fichier XML à votre projet et nommez-le web.config. Remplissez-le avec le XML suivant :
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <handlers> <remove name="httpplatformhandler" /> <add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/> </handlers> <httpPlatform stdoutLogEnabled="true" stdoutLogFile="suave.log" startupTimeLimit="20" processPath=".\publish\FivePM.exe" arguments="%HTTP_PLATFORM_PORT%"/> </system.webServer> </configuration>Connectez-vous au serveur FTP en utilisant les informations d'identification obtenues auprès du centre de déploiement (vous devrez cliquer sur l'option FTP). Déplacez le fichier web.config vers le dossier wwwroot de votre site FTP de service d'application.
Nous voulons maintenant créer et publier notre application, mais avant cela, nous devons apporter une petite modification au code du serveur. Accédez à votre fonction runServer et modifiez les 3 premières lignes comme suit :
let runWebServer (argv:string[]) = // Define the port where you want to serve. We'll hardcode this for now. let port = if argv.Length = 0 then 8080 else (int argv.[0])Ce qui permet à l'application d'examiner l'argument transmis et d'utiliser le premier argument comme numéro de port plutôt que d'avoir le port codé en dur sur 8080. Dans la configuration Web, nous passons %HTTP_PLATFORM_PORT% comme premier argument, donc nous doit être réglé.
Générez l'application en mode de publication, publiez l'application et copiez le dossier publié dans le wwwroot. Redémarrez l'application et vous devriez voir le résultat de l'API JSON sur le site *.azurewebsites.net .
Maintenant notre application est déployée !
Le frontal
Maintenant que le serveur est déployé, nous pouvons créer un frontal. Pour le front-end, nous allons créer une application Android utilisant Xamarin et F#. Cette pile, comme notre environnement back-end, bénéficie d'une intégration profonde avec Visual Studio. Bien sûr, l'écosystème F# prend en charge un certain nombre d'options de développement frontal (WebSharper, Fable/Elmish, Xamarin.iOS, DotLiquid, etc.), mais par souci de brièveté, nous ne développerons qu'en utilisant Xamarin.Android pour ce post et partirons eux pour les prochains messages.
Mise en place
Pour configurer l'application Android, démarrez un nouveau projet et sélectionnez l'option Xamarin Android. Assurez-vous que les outils de développement Android sont installés. Une fois le projet configuré, vous devriez voir quelque chose comme ça dans votre fichier de code principal.
[<Activity (Label = "FivePMFinder", MainLauncher = true, Icon = "@mipmap/icon")>] type MainActivity () = inherit Activity () let mutable count:int = 1 override this.OnCreate (bundle) = base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) button.Click.Add (fun args -> button.Text <- sprintf "%d clicks!" count count <- count + 1 )Ceci est le code de démarrage pour F # Android Xamarin. Actuellement, le code ne fait que suivre le nombre de fois qu'un bouton a été cliqué et affiche la valeur de comptage actuelle. Vous pouvez le voir fonctionner en appuyant sur F5 pour lancer l'émulateur et démarrer l'application en mode débogage.
Ajout de composants d'interface utilisateur
Ajoutons quelques composants d'interface utilisateur et rendons-le plus utile. Ouvrez les ressources/mises en page et accédez à Main.axml.
Vous devriez voir une représentation visuelle de la mise en page de l'activité principale. Vous pouvez modifier les différents éléments de l'interface utilisateur en cliquant sur les éléments. Vous pouvez ajouter des éléments en accédant à la boîte à outils et en sélectionnant l'élément que vous souhaitez ajouter. Renommez le bouton et ajoutez un textView sous le bouton. La représentation XML de votre AXML devrait ressembler à ceci :
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/fivePM" /> <TextView android:text="" android:textAppearance="?android:attr/textAppearanceMedium" android:layout_width="match_parent" android:layout_height="wrap_content" android: /> </LinearLayout>L'AXML fait référence au fichier de ressources de chaînes, alors ouvrez votre fichier resources/values/strings.xml et apportez les modifications suivantes :
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="fivePM">It\'s 5PM Somewhere!</string> <string name="app_name">5PM Finder</string> </resources>Nous avons maintenant construit le frontal AXML. Maintenant, connectons-le à du code. Accédez à MainActivity.fs et apportez les modifications suivantes à votre fonction onCreate :
base.OnCreate (bundle) // Set our view from the "main" layout resource this.SetContentView (Resources.Layout.Main) // Get our button from the layout resource, and attach an event to it let button = this.FindViewById<Button>(Resources.Id.myButton) let txtView = this.FindViewById<TextView>(Resources.Id.textView1); button.Click.Add (fun args -> let webClient = new WebClient() txtView.Text <- webClient.DownloadString("https://fivepm.azurewebsites.net/") )Remplacez fivepm.azurewebsites.net par l'URL de votre propre déploiement d'API JSON. Lancez l'application et cliquez sur le bouton dans l'émulateur. Dans un instant, vous devriez voir le retour de l'API JSON avec le résultat de votre API.
Analyse JSON
Nous y sommes presque! À l'heure actuelle, notre application affiche le JSON brut et il est plutôt illisible. L'étape suivante consiste donc à analyser le JSON et à générer une chaîne plus lisible par l'homme. Pour analyser JSON, nous pouvons utiliser la bibliothèque Newtonsoft.JSON du serveur.
Accédez à votre gestionnaire de packages NuGet et recherchez Newtonsoft.JSON. Installez et revenez au fichier MainActivity.fs. Importez-le en ajoutant "open Newtonsoft.Json".
Ajoutez maintenant le type TZInfo au projet. Nous pourrions réutiliser le TZInfo du serveur, mais comme nous n'avons en fait besoin que de deux des champs, nous pouvons définir un type personnalisé ici :
type TZInfo = { tzName:string localTime: string }Ajoutez la définition de type au-dessus de la fonction principale, et modifiez maintenant la fonction principale ainsi :
button.Click.Add (fun args -> let webClient = new WebClient() let tzi = JsonConvert.DeserializeObject<TZInfo>(webClient.DownloadString("https://fivepm.azurewebsites.net/")) txtView.Text <- sprintf "It's (about) 5PM in the\n\n%s Timezone! \n\nSpecifically, it is %s there" tzi.tzName tzi.localTime )Maintenant, le résultat de l'API JSON est désérialisé dans l'objet TZInfo et utilisé pour construire une chaîne. Exécutez l'application et cliquez sur le bouton. Vous devriez voir la chaîne formatée apparaître à l'écran.
Bien que cette application soit très simple et peut-être non raffinée, nous avons construit une application mobile qui consomme une API F # JSON, transforme les données et les affiche à l'utilisateur. Et nous avons tout fait en F#. N'hésitez pas à jouer avec les différentes commandes et voyez si vous pouvez améliorer la conception.
Et là, nous l'avons! Une simple application mobile F # et une API JSON F # et indique à l'utilisateur où il est cinq heures.
Emballer
Aujourd'hui, nous avons parcouru la construction d'une API Web simple et d'une application Android simple utilisant uniquement F #, démontrant à la fois l'expressivité du langage F # et la force de l'écosystème F #. Cependant, nous avons à peine effleuré la surface du développement de F #, je vais donc écrire quelques articles supplémentaires pour développer ce dont nous avons discuté aujourd'hui. De plus, j'espère que cet article vous a inspiré pour créer vos propres applications F# !
Vous pouvez trouver le code que nous avons utilisé pour ce tutoriel sur GitHub.
