本文概述
- 为什么选择F#, F#的作用是什么?
- F#教程背后的想法
- 后端
- 前端
- 本文总结
例如, 沃尔玛已经开始将Clojure(一种基于JVM的功能Lisp方言)用于其结帐基础架构; Jet.com是一个大型电子商务平台(现已由沃尔玛拥有), 使用F#构建其大多数微服务。专有贸易公司Jane Street则主要使用OCaml构建其算法。
今天, 我们将探讨F#编程。 F#是一种功能性编程语言, 由于其灵活性, 强大的.NET集成以及高质量的可用工具, 因此越来越受到采用。出于本F#教程的目的, 我们将在前端和后端仅使用F#来构建一个简单的Web服务器和相关的移动应用程序。
为什么选择F#, F#的作用是什么? 对于今天的项目, 我们将只使用F#。选择F#作为我们选择的语言有几个原因:
- .NET集成:F#与.NET世界的其余部分紧密集成, 因此可以访问具有良好支持和详尽文档库的大型生态系统, 以解决各种编程任务。
- 简洁:F#具有强大的类型推断系统和简洁的语法, 因此极为简洁。与C#或Java相比, 使用F#可以更轻松地解决编程任务。通过比较, F#代码看起来非常精简。
- 开发人员工具:F#拥有强大的集成Visual Studio, 它是.NET生态系统的最佳IDE之一。对于那些在非Windows平台上工作的人, Visual Studio代码中有大量的插件。这些工具使使用F#进行编程的效率非常高。
F#教程背后的想法 【F#教程(如何构建完整的F#应用程序)】在美国, 有一种流行的说法:” 某处五点钟” 。
在世界上的某些地区, 下午5:00是最早在社交上可以喝一杯或喝杯传统茶的时间。
今天, 我们将基于此概念构建一个应用程序。我们将构建一个应用程序, 该应用程序可以在任何给定的时间搜索各个时区, 找出现在的5点钟, 然后将该信息提供给用户。
后端 设置Web服务器
我们将从制作执行时区搜索功能的后端服务开始。我们将使用Suave.IO来构建JSON API。
文章图片
Suave.IO是带有轻量级Web服务器的直观Web框架, 该服务器允许快速地对简单的Web应用程序进行编码。
首先, 请转到Visual Studio并启动一个新的F#控制台应用程序项目。如果你无法使用此选项, 则可能需要使用Visual Studio Installer安装F#功能。将项目命名为” FivePM” 。创建应用程序后, 你应该会看到以下内容:
[<
EntryPoint>
]let main argv = printfn "%A" argv0 // return an integer exit code
这是一段非常简单的入门代码, 它打印出参数并以状态代码0退出。可以随意更改打印语句并尝试各种代码功能。 “ %A” 格式化程序是一种特殊的格式化程序, 可以打印你传入的任何类型的字符串表示形式, 因此可以随意打印整数, 浮点数甚至复杂的类型。熟悉基本语法后, 就该安装Suave了。
安装Suave的最简单方法是通过NuGet软件包管理器。转到项目-> 管理NuGet软件包, 然后单击浏览选项卡。搜索Suave, 然后单击安装。一旦你接受要安装的软件包, 就应该准备就绪!现在回到你的program.fs屏幕, 我们准备开始构建服务器。
要开始使用Suave, 我们需要先导入软件包。在程序的顶部, 键入以下语句:
open Suaveopen Suave.Operatorsopen Suave.Filtersopen Suave.Successful
这将导入构建基本Web服务器所需的基本软件包。现在, 用以下代码替换main中的代码, 该代码定义了一个简单的应用程序并将其提供给端口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 portlet cfg ={ defaultConfig withbindings = [ 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 serverstartWebServer cfg app0
即使你不熟悉F#语法或Suave定义路由处理程序的方式, 代码也应该看起来非常简单明了, 代码也应该相当易读。本质上, 该Web应用返回的状态为200, 并且显示” Hello World!” 。在” /” 路径上被GET请求击中时返回的字符串。继续运行该应用程序(在Visual Studio中为F5)并导航到localhost:8080, 你应该看到” Hello World!” 。在浏览器窗口中。
重构服务器代码
现在我们有了一个Web服务器!不幸的是, 它并不能起到很多作用-因此, 让我们为它提供一些功能!首先, 让我们将网络服务器功能移到其他位置, 以便我们构建某些功能而不必担心网络服务器(稍后我们将其连接到网络服务器)。因此定义一个单独的函数:
// 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 portlet cfg ={ defaultConfig withbindings = [ 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 serverstartWebServer cfg app
现在将main函数更改为以下内容, 并确保我们做对了。
[<
EntryPoint>
]let main argv = runWebServer argv0
按下F5键, 进入我们的” Hello World!” 服务器应该像以前一样运行。
获取时区
现在, 我们来构建确定五点钟所在时区的功能。我们要编写一些代码以遍历所有时区, 并确定最接近5:00 pm的时区。
文章图片
此外, 我们并不是真的想返回一个非常接近5:00 pm但稍早于(例如4:58 pm)的时区, 因为在此演示中, 前提是不能在5之前:00 pm, 但关闭。
首先, 获取时区列表。在F#中, 这非常容易, 因为它与C#集成得很好。在顶部添加” 开放系统” , 然后将F#应用程序更改为此:
[<
EntryPoint>
]let main argv = // This gets all the time zones into a List-like objectlet tzs = TimeZoneInfo.GetSystemTimeZones()// Now we iterate through the list and print out the names of the timezonesfor tz in tzs doprintfn "%s" tz.DisplayName0
运行该应用程序, 你应该在控制台中看到所有时区, 其偏移量和显示名称的列表。
创建和使用自定义类型
现在我们有了时区列表, 我们可以将它们转换为对我们更有用的自定义数据类型, 其中包含诸如UTC偏移量, 本地时间, 本地时间从下午5:00到多远的信息。等等。为此, 让我们在主函数上方定义一个自定义类型:
type TZInfo = {tzName: string;
minDiff: float;
localTime: string;
utcOffset: float}
现在, 我们可以将从上一步获得的时区信息转换为该TZInfo对象的列表。从而更改你的主要功能:
[<
EntryPoint>
]let main argv = // This gets all the time zones into a List-like objectlet tzs = TimeZoneInfo.GetSystemTimeZones()// List comprehension + type inference allows us to easily perform conversionslet tzList = [for tz in tzs do// convert the current time to the local time zonelet 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).TotalMinutesyield {tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}]printfn "%A" tzList.Head0
并且你应该会在屏幕上看到Dateline Standard时间的tzInfo对象。
排序, 过滤和管道, 我的天哪!
现在我们有了这些tzInfo对象的列表, 我们可以对这些对象进行过滤和排序, 以找到1)5:00 pm之后的时区和2)最接近1)中时区5:00 pm的时区。像这样更改你的主要功能:
[<
EntryPoint>
]let main argv = // This gets all the time zones into a List-like objectlet tzs = TimeZoneInfo.GetSystemTimeZones()// List comprehension + type inference allows us to easily perform conversionslet tzList = [for tz in tzs do// convert the current time to the local time zonelet 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).TotalMinutesyield {tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}]// We use the pipe operator to chain functiona calls togetherlet 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.headprintfn "%A" closest
现在, 我们应该有了所需的时区。
将时区获取器重构为其自己的功能
现在, 让我们将代码重构为自己的功能, 以便以后使用。因此定义一个函数:
// the function takes uint as input, and we represent that as "()"let getClosest () = // This gets all the time zones into a List-like objectlet tzs = TimeZoneInfo.GetSystemTimeZones()// List comprehension + type inference allows us to easily perform conversionslet tzList = [for tz in tzs do// convert the current time to the local time zonelet 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).TotalMinutesyield {tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}]// We use the pipe operator to chain function calls togethertzList // 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.headAnd our main function can just be:[<
EntryPoint>
]let main argv = printfn "%A" <
| getClosest()0
运行代码, 你将看到与以前相同的输出。
JSON编码返回数据
现在我们可以获得时区数据, 我们可以将信息转换为JSON并通过我们的应用程序提供服务。这非常简单, 这要感谢NewtonSoft提供的JSON.NET软件包。返回你的NuGet软件包管理器, 找到Newtonsoft.Json, 然后安装该软件包。现在返回Program.fs并对我们的主要功能进行一些更改:
[<
EntryPoint>
]let main argv = printfn "%s" <
| JsonConvert.SerializeObject(getClosest())0
立即运行代码, 而不是TZInfo对象, 你应该看到JSON打印到控制台。
将时区信息连接到JSON API
将其连接到我们的JSON API非常简单。只需对你的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 portlet cfg ={ defaultConfig withbindings = [ 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 MimeTypepath "/" >
=>
request (fun _ ->
OK <
| JsonConvert.SerializeObject(getClosest()))>
=>
setMimeType "application/json;
charset=utf-8"]]// Now we start the serverstartWebServer cfg app
运行该应用程序, 然后导航到localhost:8080。你应该在浏览器窗口中看到JSON。
部署服务器
现在我们有了JSON API服务器, 我们可以对其进行部署, 以便可以在Internet上对其进行访问。部署此应用程序最简单的方法之一就是通过Microsoft Azure的应用程序服务, 该服务可以理解为托管IIS服务。若要部署到Azure App服务, 请转到https://portal.azure.com并转到App Service。创建一个新应用, 然后导航到门户中的部署中心。如果你是第一次访问该门户网站, 可能会有些不知所措, 因此, 如果你有困难, 请务必查阅其中的许多教程之一以使用App Service。
你应该看到各种各样的部署选项。你可以使用任何你喜欢的东西, 但是为了简单起见, 我们可以使用FTP选项。
App服务在你的应用程序的根目录下查找一个web.config文件, 以了解如何运行你的应用程序。由于我们的Web服务器是必不可少的控制台应用程序, 因此我们可以使用HttpPlatformHandler发布该应用程序并与IIS服务器集成。在Visual Studio中, 将XML文件添加到你的项目中, 并将其命名为web.config。用以下XML填充它:
<
?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>
使用从部署中心获得的凭据连接到FTP服务器(你将需要单击FTP选项)。将web.config移至应用程序服务FTP站点的wwwroot文件夹。
现在, 我们要构建和发布我们的应用程序, 但是在开始之前, 我们需要对服务器代码进行一些小的更改。转到你的runServer函数, 并将前三行更改为以下内容:
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])
这允许应用程序查看传入的参数, 并使用第一个参数作为端口号, 而不是将端口硬编码为8080。在Web配置中, 我们将%HTTP_PLATFORM_PORT%作为第一个参数传递, 因此我们应该设置。
在发布模式下构建应用程序, 发布应用程序, 然后将发布的文件夹复制到wwwroot。重新启动应用程序, 你应该在* .azurewebsites.net站点上看到JSON API结果。
现在我们的应用程序已部署!
前端
文章图片
现在我们已经部署了服务器, 我们可以构建一个前端。对于前端, 我们将使用Xamarin和F#构建一个Android应用程序。像我们的后端环境一样, 此堆栈与Visual Studio进行了深度集成。当然, F#生态系统支持许多前端开发选项(WebSharper, Fable / Elmish, Xamarin.iOS, DotLiquid等), 但是为了简洁起见, 我们仅在本文中使用Xamarin.Android进行开发, 并离开他们为将来的职位。
配置
要设置Android应用, 请启动一个新项目, 然后选择Xamarin Android选项。确保你已安装Android开发工具。设置项目后, 你应该在主代码文件中看到类似这样的内容。
[<
Activity (Label = "FivePMFinder", MainLauncher = true, Icon = "@mipmap/icon")>
]type MainActivity () =inherit Activity ()let mutable count:int = 1override this.OnCreate (bundle) =base.OnCreate (bundle)// Set our view from the "main" layout resourcethis.SetContentView (Resources.Layout.Main)// Get our button from the layout resource, and attach an event to itlet button = this.FindViewById<
Button>
(Resources.Id.myButton)button.Click.Add (fun args ->
button.Text <
- sprintf "%d clicks!" countcount <
- count + 1)
这是F#Android Xamarin的入门代码。该代码当前仅跟踪单击按钮的次数并显示当前计数值。你可以通过按F5键启动仿真器并以调试模式启动应用程序来查看其工作情况。
添加UI组件
让我们添加一些UI组件, 使其更有用。打开资源/布局, 然后导航到Main.axml。
你应该看到主要活动布局的直观表示。你可以通过单击元素来编辑各种UI元素。你可以通过转到工具箱并选择要添加的元素来添加元素。重命名按钮, 并在按钮下方添加一个textView。 AXML的XML表示应类似于以下内容:
<
?xml version="1.0" encoding="utf-8"?>
<
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent">
<
Button android:id="@+id/myButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/fivePM" />
<
TextViewandroid:text=""android:textAppearance="?android:attr/textAppearanceMedium"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/textView1" />
<
/LinearLayout>
AXML引用了字符串资源文件, 因此打开你的resources / values / strings.xml并进行以下更改:
<
?xml version="1.0" encoding="utf-8"?>
<
resources>
<
string name="fivePM">
It\'s 5PM Somewhere!<
/string>
<
string name="app_name">
5PM Finder<
/string>
<
/resources>
现在, 我们已经构建了前端AXML。现在, 将其连接到一些代码。导航到MainActivity.fs并对onCreate函数进行以下更改:
base.OnCreate (bundle)// Set our view from the "main" layout resourcethis.SetContentView (Resources.Layout.Main)// Get our button from the layout resource, and attach an event to itlet 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/"))
用你自己的JSON API部署的URL替换fourpm.azurewebsites.net。运行该应用程序, 然后单击仿真器中的按钮。稍后, 你应该会看到JSON API随API结果一起返回。
解析JSON
我们快要到了!目前, 我们的应用程序正在显示原始JSON, 这非常不可读。然后, 下一步是解析JSON并输出更易于理解的字符串。要解析JSON, 我们可以使用服务器上的Newtonsoft.JSON库。
导航到你的NuGet程序包管理器, 然后搜索Newtonsoft.JSON。安装并返回MainActivity.fs文件。通过添加” open Newtonsoft.Json” 将其导入。
现在将TZInfo类型添加到项目中。我们可以从服务器重用TZInfo, 但是由于实际上我们只需要两个字段, 因此可以在此处定义自定义类型:
type TZInfo = {tzName:stringlocalTime: string}
在main函数上方添加类型定义, 然后更改main函数:
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)
现在, JSON API结果反序列化为TZInfo对象, 并用于构造字符串。运行该应用程序, 然后单击按钮。你应该看到格式化的字符串弹出到屏幕上。
尽管此应用程序非常简单, 甚至可能尚未完善, 但我们构建了一个移动应用程序, 该应用程序使用F#JSON API, 转换数据并将其显示给用户。而我们在F#中都做到了。随意使用各种控件, 看看是否可以改进设计。
我们终于得到它了!一个简单的F#移动应用程序和一个F#JSON API, 并告诉用户五点钟的位置。
本文总结 今天, 我们逐步完成了仅使用F#构建一个简单的Web API和一个简单的Android应用程序的过程, 展示了F#语言的表现力和F#生态系统的优势。但是, 我们几乎没有涉及F#开发的内容, 因此我将在我们今天讨论的内容的基础上再写几篇文章。此外, 我希望本文能激发你构建自己的F#应用程序的灵感!
你可以在GitHub上找到我们用于本教程的代码。我还构建了一个带有Web前端和一些其他功能的版本, 你可以在https://fivepm.azurewebsites.net上找到这些版本。
推荐阅读
- Redux状态管理的顶级控制(ClojureScript教程)
- 作为JS开发人员,这就是让我彻夜难眠的原因
- ActiveResource.js(快速为你的JSON API构建强大的JavaScript SDK)
- React教程(如何入门及其比较(1))
- 你的WordPress API开发人员未使用的五种经过久经考验的技术
- 老师节日快乐!教师节祝福微信gif表情包_微信
- 适合在教师节这一天公布的微信朋友圈图文说说_微信
- 简聊怎样用?简聊注册与登录运用图文详细教程_其它聊天
- qq厘米秀逃离失控人类活动在啥地方?